Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve lib.py. #790

Merged
merged 28 commits into from
Nov 29, 2024
Merged

Improve lib.py. #790

merged 28 commits into from
Nov 29, 2024

Conversation

knutnergaard
Copy link
Contributor

No description provided.

knutnergaard and others added 20 commits November 20, 2024 14:08
- Add type annotation.
- Add/improve documentation.
- changed `BaseSegments.segments` to return `tuple` instead of `list`.
Remaining errors to be collected in robotools#775:

```zsh
contour.py:58: error: Signature of "_reprContents" incompatible with supertype "BaseObject"  [override]
contour.py:58: note:      Superclass:
contour.py:58: note:          @classmethod
contour.py:58: note:          def _reprContents(cls) -> List[str]
contour.py:58: note:      Subclass:
contour.py:58: note:          def _reprContents(self) -> List[str]
contour.py:125: error: Incompatible types in assignment (expression has type "Callable[[], Any]", variable has type "Optional[BaseGlyph]")  [assignment]
contour.py:125: error: Argument 1 to "reference" has incompatible type "BaseGlyph"; expected "Callable[[], Any]"  [arg-type]
contour.py:277: error: "BaseContour" has no attribute "_getIdentifierforPoint"; maybe "_getIdentifierForPoint" or "getIdentifierForPoint"?  [attr-defined]
contour.py:477: error: Signature of "isCompatible" incompatible with supertype "InterpolationMixin"  [override]
contour.py:477: note:      Superclass:
contour.py:477: note:          def isCompatible(self, other: Any, cls: Type[Any]) -> Tuple[bool, Any]
contour.py:477: note:      Subclass:
contour.py:477: note:          def isCompatible(self, other: BaseContour) -> Tuple[bool, str]
contour.py:532: error: "str" has no attribute "fatal"  [attr-defined]
contour.py:532: error: "str" has no attribute "warning"  [attr-defined]
contour.py:533: error: "str" has no attribute "fatal"  [attr-defined]
contour.py:535: error: "str" has no attribute "warning"  [attr-defined]
contour.py:657: error: "BaseContour" has no attribute "_reverseContour"  [attr-defined]
```
@knutnergaard knutnergaard marked this pull request as ready for review November 25, 2024 15:13
@knutnergaard knutnergaard changed the title Lib Improve lib.py. Nov 25, 2024
@knutnergaard
Copy link
Contributor Author

Several of the BaseLib methods document list of str items as the only acceptable type for dict values. Since at least fontshell allows value to be of any type, I've changed the docs to reflect Any.

@knutnergaard
Copy link
Contributor Author

Remaining errors after ae7328f to be collected in #775:

lib.py:34: error: Signature of "_reprContents" incompatible with supertype "BaseObject"  [override]
lib.py:34: note:      Superclass:
lib.py:34: note:          @classmethod
lib.py:34: note:          def _reprContents(cls) -> List[str]
lib.py:34: note:      Subclass:
lib.py:34: note:          def _reprContents(self) -> List[str]
lib.py:75: error: "BaseGlyph" not callable  [operator]
lib.py:80: error: "BaseGlyph" not callable  [operator]
lib.py:83: error: Incompatible types in assignment (expression has type "Callable[[], Any]", variable has type "Optional[BaseGlyph]")  [assignment]
lib.py:83: error: Argument 1 to "reference" has incompatible type "BaseGlyph"; expected "Callable[[], Any]"  [arg-type]
lib.py:112: error: "BaseFont" not callable  [operator]
lib.py:118: error: "BaseFont" not callable  [operator]
lib.py:123: error: Incompatible types in assignment (expression has type "Callable[[], Any]", variable has type "Optional[BaseFont]")  [assignment]
lib.py:123: error: Argument 1 to "reference" has incompatible type "BaseFont"; expected "Callable[[], Any]"  [arg-type]

@benkiel
Copy link
Member

benkiel commented Nov 26, 2024

@typesupply are you ok with relaxing the value to be any type?

@knutnergaard
Copy link
Contributor Author

normalizeLibValue also allows any type except None, btw.

@typesupply
Copy link
Member

The value limitations in methods like __setitem__ are incorrect. The lib is constrained by the property list requirements. Keys must be strings. Values can be any type that can be written into a property list value. I recall that normalizing values was incredibly hard because determining if something can be converted to property list from the Python representation requires a ton of code. Property lists have no concept of "nothing", so None is always rejected.

@typesupply
Copy link
Member

From plistlib:

Values can be strings, integers, floats, booleans, tuples, lists, dictionaries (but only with string keys), bytes, bytearray or datetime.datetime objects.

Maybe we could just point to the plistlib doc instead of enumerating everything ourselves. Or we could do what the UFO spec does and point to Apple's DTD.

@knutnergaard
Copy link
Contributor Author

For the sake of clarity and explicitness, I suggest extending normalizeLibValue to accommodate/reject the types specified in the plistlib docs, and annotate BaseLib to reflect these changes, e.g., with LibValueType or similarly appropriate alias. Unless I'm misunderstanding, I don't see why this would require tons more code. The isinstance in the normalizer just has to evaluate a list of several types instead of just None.

@typesupply
Copy link
Member

My concern back then with using isinstance was that there are many situations where the incoming object isn't an instance of the standard Python types, but behaves like one. My concern was primarily focused on environments that use Apple's AppKit (notably RoboFont). The values there returned by controls aren't automatically converted from the AppKit types (NSInteger, NSString, etc.) to their Python equivalents. I was worried about plistlib choking on these. (My memory is hazy, but I seem to recall this happening sometimes. It looks like PyObjC now handles isinstance for at least strings. Dictionaries still fail, but they would fail in plistlib so that's not a concern.) My plan way back in the day was to do the conversions manually inside of normalizeLibValue but that was a huge amount of work and I let plistlib do the validation on UFO write.

I just looked at the plistlib code and it does something similar to what you suggest: a sequence of isinstance tests. We could make LibValueType all of the types that plistlib tests for and use that in normalizeLibValue as you suggest.

Thanks for pressing on this!

@benkiel
Copy link
Member

benkiel commented Nov 26, 2024

Yes, I agree with Tal; changing the validator to test for the accepted types (and recursively for dictionaries) is the thing to do here. Thank you @knutnergaard for pushing on this!

@knutnergaard
Copy link
Contributor Author

My pressure!

Since I'm already working on BasLib, I can take a crack at the normalization and annotation improvements.

knutnergaard and others added 5 commits November 27, 2024 11:39
Changes in normalizers.py include:
- Handle normalization of specific plistlib-compatible types in `normalizeLibValue`.
- Resolve `mypy` errors.
- Improve error messages and efficiency in certain functions.
@knutnergaard
Copy link
Contributor Author

@benkiel @typesupply I ended up referencing a new common value type type-lib-value in the documentation, rather than listing all the possible types. The TypeError in the normalizer does list all the types, though.

@benkiel benkiel merged commit ac41f47 into robotools:v1 Nov 29, 2024
10 checks passed
@benkiel
Copy link
Member

benkiel commented Nov 29, 2024

@knutnergaard thank you for this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants