+
Skip to content

Conversation

schriftgestalt
Copy link
Contributor

Add some type hints.


def __eq__(self, other):
if type(self) != type(other):
if not isinstance(self, type(other)):
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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):
Copy link
Collaborator

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.

@schriftgestalt
Copy link
Contributor Author

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?

@jenskutilek
Copy link
Collaborator

I think you need to from __future__ import annotations for the tests to pass in Python 3.9.

And you should format the code with black to get rid of the linter fails.

Copy link

@jorenham jorenham left a 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
Copy link

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/

_tableCache=None,
cfg={},
):
file: str | IO[bytes] | Any | None = None, # Using Any for generic file-like objects
Copy link

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"?

Copy link
Collaborator

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
Copy link

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.

Copy link
Collaborator

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
Copy link

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

Suggested change
quiet: bool | None = None, # Deprecated
quiet: None = None, # Deprecated

Copy link
Collaborator

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,
Copy link

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 ;)

Copy link
Collaborator

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,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
Copy link

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?

Copy link
Collaborator

@madig madig Oct 7, 2025

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.

Copy link

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

Copy link
Collaborator

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.

Copy link

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

Comment on lines +439 to +433
report = "No '%s' table found." % tag
log.info(report)
Copy link

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:

Suggested change
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.

Copy link
Collaborator

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:
Copy link

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,
Copy link

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.

Copy link
Collaborator

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.

Copy link

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)

@schriftgestalt
Copy link
Contributor Author

I currently have no time to work on this. Is there a way to work on this together?

[skip ci]
@madig madig marked this pull request as draft October 8, 2025 14:41
@jorenham
Copy link

jorenham commented Oct 8, 2025

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",
Copy link
Collaborator

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...

@schriftgestalt
Copy link
Contributor Author

there are a lot of good suggestions. Someone needs to put them in.
I’m not sure how that works git wise. Can you add commits to my fork?

if hasattr(current_file, "seekable"):
seekable = current_file.seekable() # type: ignore
elif hasattr(current_file, "seek"):
try:
Copy link
Collaborator

@madig madig Oct 8, 2025

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?

@jorenham
Copy link

jorenham commented Oct 8, 2025

there are a lot of good suggestions. Someone needs to put them in. I’m not sure how that works git wise. Can you add commits to my fork?

I'm not a maintainer here, so not directly. I could PR to your PR branch on your fork I suppose.

@madig
Copy link
Collaborator

madig commented Oct 8, 2025

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.

@anthrotype
Copy link
Member

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

madig added a commit that referenced this pull request Oct 8, 2025
madig added a commit that referenced this pull request Oct 9, 2025
Co-authored-by: schriftgestalt <georg.seifert@mac.com>
madig added a commit that referenced this pull request Oct 9, 2025
Co-authored-by: schriftgestalt <georg.seifert@mac.com>
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.

6 participants

点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载