-
Notifications
You must be signed in to change notification settings - Fork 493
Type hints #3826
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
base: main
Are you sure you want to change the base?
Type hints #3826
Conversation
|
||
def __eq__(self, other): | ||
if type(self) != type(other): | ||
if not isinstance(self, type(other)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unrelated to type hints and changes the meaning of the code. The current code is written as intended: only compare when the types match exactly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. I tried to avoid changes like this. I’ll remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flake complains about the !=
. Can this changed to is not
?
assert writer.items[lengthIndex] == b"\xde\xad\xbe\xef"[: conv.staticSize] | ||
writer.items[lengthIndex] = lengthWriter.getAllData() | ||
|
||
def __repr__(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recomment to make a separate PR for changes other than type annotations.
I fixed a few flake issues I missed. But tox is not happy at all when I run it locally. Is there anything I can do or can I ignore issues in files I didn’t touch? I have fixes some easy ones. But that is probably another PR? |
I think you need to And you should format the code with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a completely unsolicited review; I hope you don't mind :)
from io import BytesIO | ||
from fontTools import cffLib | ||
from . import DefaultTable | ||
from typing import Any, Dict, List, Optional, TYPE_CHECKING |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need for Dict
, List
anymore since Python 3.9 :)
https://peps.python.org/pep-0585/
Lib/fontTools/ttLib/ttFont.py
Outdated
_tableCache=None, | ||
cfg={}, | ||
): | ||
file: str | IO[bytes] | Any | None = None, # Using Any for generic file-like objects |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does os.PathLike
(docs) fit the bill for "generic file-like objects"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PathLike is for paths, like strings or Path objects. https://docs.python.org/3/library/typing.html#typing.IO is for File-like objects.
checkChecksums: int = 0, | ||
verbose: bool | None = None, # Deprecated | ||
recalcBBoxes: bool = True, | ||
allowVID: Any = NotImplemented, # Deprecated/Unused |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once Python 3.9 support is no longer required, [types.NotImplementedType](https://docs.python.org/3/library/types.html#types.NotImplementedType)
(docs) could be used for this. Otherwise, you could import it from typing_extensions
from within a if TYPE_CHECKING:
block (which magically doesn't require typing_extensions
to be installed), and stringify-annotate it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hrm, I'd prefer a https://docs.python.org/3/library/exceptions.html#DeprecationWarning to be emitted if this parameter is used, rather than the type/default value being lied about.
recalcTimestamp: bool = True, | ||
fontNumber: int = -1, | ||
lazy: bool | None = None, | ||
quiet: bool | None = None, # Deprecated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to avoid people from using this, perhaps it's an idea to annotate it as
quiet: bool | None = None, # Deprecated | |
quiet: None = None, # Deprecated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hrm, I'd prefer a https://docs.python.org/3/library/exceptions.html#DeprecationWarning to be emitted if this parameter is used, rather than the type being lied about.
fontNumber: int = -1, | ||
lazy: bool | None = None, | ||
quiet: bool | None = None, # Deprecated | ||
_tableCache: dict[tuple[Tag, bytes], DefaultTable] | None = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep in mind that both type parameters of dict
are invariant. So if you have
class DefaultTableSubclass(DefaultTable): ...
my_table_cache: dict[tuple[Tag, bytes], DefaultTableSubclass] = ...
then you can't pass my_table_cache
as _tableCache
here. So the value types must be exactly the same.
This is also the case for the key type, so Tag
subclasses won't work.
So if that's not what you want, then you could use collections.abc.Mapping
, which has a covariant value type, but the key type is unfortunately also invariant (which is a known issue). To get around that, you could use a "free" type parameter, e.g. _TagT = TypeVar("_TagT", bound=Tag)
, and use that instead of Tag
. Since it's not associated to anything else, it can materialize to anything assignable to Tag
, effectively emulating covariant behavior ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meaning that TTFont would need to use generics like so: class TTFont(Generic[_TagT])
?
@anthrotype I don't think we ever subclass Tag, no? I see no reason to...
def __exit__(self, type, value, traceback): | ||
def __exit__( | ||
self, | ||
exc_type: Type[BaseException] | None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exc_type: Type[BaseException] | None, | |
exc_type: type[BaseException] | None, |
def saveXML(self, fileOrPath, newlinestr="\n", **kwargs): | ||
def saveXML( | ||
self, | ||
fileOrPath: str | IO[str] | Any, # Use Any for generic text stream |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe os.PathLike[str]
could replace Any
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather, os.PathLike[str] | IO[str]
instead of str | IO[str] | Any
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
str
won't be assignable to that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh. I forgot that PathLike is a special snowflake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I suppose there's _typeshed.StrPath
you could use
report = "No '%s' table found." % tag | ||
log.info(report) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize this is (also) out of scope, but the recommended way of formatting logging calls would in this case look something like this:
report = "No '%s' table found." % tag | |
log.info(report) | |
log.info( "No %r table found.", tag) |
The %r
effectively does a repr(_)
, which gives you free quotes.
And to be honest I can't remember exactly why the format arguments should be passed like this, just that there's a good reason for doing so haha.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the string interpolation will only happen when the log happens, that's why they recommend doing it the way you suggest. https://docs.astral.sh/ruff/rules/logging-percent-format/
del self.reader[tag] | ||
|
||
def get(self, tag, default=None): | ||
def get(self, tag: str, default: Any | None = None) -> DefaultTable | Any | None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it might be nice to @overload
this, and use a TypeVar
for the default, similar to Mapping.get
in https://github.com/python/typeshed/blob/aa52024657e7aecfe03d08a7e3719a2ded9a907a/stdlib/typing.pyi#L768-L773
): | ||
self, | ||
preferCFF: bool = True, | ||
location: dict[str, float] | None = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this won't allow you to pass my_location: dict[str, int]
, but I'm not sure if that's such a bad thing here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One could turn this into dict[str, float] | dict[str, int] | None
. Not sure if there's a more elegant way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use the dict[str, T]
trick, with T = TypeVar("T", bound=float)
I currently have no time to work on this. Is there a way to work on this together? |
I wouldn't mind helping out. What do you propose? |
str | IO[bytes] | Any | None | ||
) = None, # Using Any for generic file-like objects | ||
res_name_or_index: str | int | None = None, | ||
sfntVersion: bytes = b"\000\001\000\000", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more correct, but there is code that checks for a str, not bytes...
there are a lot of good suggestions. Someone needs to put them in. |
if hasattr(current_file, "seekable"): | ||
seekable = current_file.seekable() # type: ignore | ||
elif hasattr(current_file, "seek"): | ||
try: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason behind this change?
I'm not a maintainer here, so not directly. I could PR to your PR branch on your fork I suppose. |
I can push to the branch of your fork. I'm currently scratching my head a bit though, because there are various code changes that go way beyond adding type hints and break tests. I'm contemplating breaking off pieces of this PR and focussing on adding type hints without changing anything else. |
strong yes please. We already had a regression which slipped through another similar big type-hinting releated PR (for ufoLib) recently |
Co-authored-by: schriftgestalt <georg.seifert@mac.com>
Co-authored-by: schriftgestalt <georg.seifert@mac.com>
Add some type hints.