From 94eec63d3ad47066f0a1d2f861fa3cc8e5efd2a0 Mon Sep 17 00:00:00 2001 From: Nikolaus Waxweiler Date: Tue, 9 Nov 2021 12:36:07 +0000 Subject: [PATCH 1/3] Use annotations import This is going to become the default in a future Python version. Also run pyupgrade over all files. --- mypy.ini | 2 +- pyproject.toml | 2 +- src/ufoLib2/__init__.py | 2 + src/ufoLib2/constants.py | 2 + src/ufoLib2/errors.py | 3 + src/ufoLib2/objects/__init__.py | 2 + src/ufoLib2/objects/anchor.py | 10 +- src/ufoLib2/objects/component.py | 11 +- src/ufoLib2/objects/contour.py | 30 ++- src/ufoLib2/objects/dataSet.py | 4 +- src/ufoLib2/objects/features.py | 2 + src/ufoLib2/objects/font.py | 75 +++----- src/ufoLib2/objects/glyph.py | 78 ++++---- src/ufoLib2/objects/guideline.py | 14 +- src/ufoLib2/objects/image.py | 12 +- src/ufoLib2/objects/imageSet.py | 4 +- src/ufoLib2/objects/info.py | 254 ++++++++++++------------- src/ufoLib2/objects/layer.py | 44 ++--- src/ufoLib2/objects/layerSet.py | 26 +-- src/ufoLib2/objects/misc.py | 51 ++--- src/ufoLib2/objects/point.py | 12 +- src/ufoLib2/pointPens/glyphPointPen.py | 22 ++- src/ufoLib2/typing.py | 8 +- tests/conftest.py | 2 + tests/objects/test_component.py | 2 + tests/objects/test_contour.py | 2 + tests/objects/test_datastore.py | 2 + tests/objects/test_font.py | 2 + tests/objects/test_glyph.py | 2 + tests/objects/test_layer.py | 2 + tests/objects/test_object_lib.py | 2 + tests/test_ufoLib2.py | 2 + 32 files changed, 344 insertions(+), 344 deletions(-) diff --git a/mypy.ini b/mypy.ini index 0fdf6b00..1d466125 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,5 +1,5 @@ [mypy] -python_version = 3.6 +python_version = 3.7 # Untyped definitions and calls disallow_incomplete_defs = True diff --git a/pyproject.toml b/pyproject.toml index 2d81022c..92df10e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=45", "wheel", "setuptools_scm>=6.2"] build-backend = "setuptools.build_meta" [tool.black] -target-version = ["py36"] +target-version = ["py37"] [tool.isort] multi_line_output = 3 diff --git a/src/ufoLib2/__init__.py b/src/ufoLib2/__init__.py index 85b77ebc..fdf09ce5 100644 --- a/src/ufoLib2/__init__.py +++ b/src/ufoLib2/__init__.py @@ -1,5 +1,7 @@ """ufoLib2 -- a package for dealing with UFO fonts.""" +from __future__ import annotations + from ufoLib2.objects import Font try: diff --git a/src/ufoLib2/constants.py b/src/ufoLib2/constants.py index 9aaca5ce..ff568f3b 100644 --- a/src/ufoLib2/constants.py +++ b/src/ufoLib2/constants.py @@ -1,3 +1,5 @@ +from __future__ import annotations + DEFAULT_LAYER_NAME: str = "public.default" """The name of the default layer.""" diff --git a/src/ufoLib2/errors.py b/src/ufoLib2/errors.py index 64f3f65c..292b963d 100644 --- a/src/ufoLib2/errors.py +++ b/src/ufoLib2/errors.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + class Error(Exception): """The base exception for ufoLib2.""" diff --git a/src/ufoLib2/objects/__init__.py b/src/ufoLib2/objects/__init__.py index 16e2a5fa..753e91df 100644 --- a/src/ufoLib2/objects/__init__.py +++ b/src/ufoLib2/objects/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from ufoLib2.objects.anchor import Anchor from ufoLib2.objects.component import Component from ufoLib2.objects.contour import Contour diff --git a/src/ufoLib2/objects/anchor.py b/src/ufoLib2/objects/anchor.py index ead84511..c6ef0edb 100644 --- a/src/ufoLib2/objects/anchor.py +++ b/src/ufoLib2/objects/anchor.py @@ -1,4 +1,4 @@ -from typing import Optional, Tuple +from __future__ import annotations from attr import define @@ -18,16 +18,16 @@ class Anchor(AttrDictMixin): y: float """The y coordinate of the anchor.""" - name: Optional[str] = None + name: str | None = None """The name of the anchor.""" - color: Optional[str] = None + color: str | None = None """The color of the anchor.""" - identifier: Optional[str] = None + identifier: str | None = None """The globally unique identifier of the anchor.""" - def move(self, delta: Tuple[float, float]) -> None: + def move(self, delta: tuple[float, float]) -> None: """Moves anchor by (x, y) font units.""" x, y = delta self.x += x diff --git a/src/ufoLib2/objects/component.py b/src/ufoLib2/objects/component.py index 5980d50f..94070f6a 100644 --- a/src/ufoLib2/objects/component.py +++ b/src/ufoLib2/objects/component.py @@ -1,5 +1,6 @@ +from __future__ import annotations + import warnings -from typing import Optional, Tuple from attr import define, field from fontTools.misc.transform import Identity, Transform @@ -29,10 +30,10 @@ class Component: transformation: Transform = field(default=Identity, converter=_convert_transform) """The affine transformation to apply to the :attr:`.Component.baseGlyph`.""" - identifier: Optional[str] = None + identifier: str | None = None """The globally unique identifier of the component.""" - def move(self, delta: Tuple[float, float]) -> None: + def move(self, delta: tuple[float, float]) -> None: """Moves this component by (x, y) font units. NOTE: This interprets the delta to be the visual delta, as in, it @@ -47,7 +48,7 @@ def move(self, delta: Tuple[float, float]) -> None: xx, xy, yx, yy, dx, dy = self.transformation self.transformation = Transform(xx, xy, yx, yy, dx + x, dy + y) - def getBounds(self, layer: GlyphSet) -> Optional[BoundingBox]: + def getBounds(self, layer: GlyphSet) -> BoundingBox | None: """Returns the (xMin, yMin, xMax, yMax) bounding box of the component, taking the actual contours into account. @@ -56,7 +57,7 @@ def getBounds(self, layer: GlyphSet) -> Optional[BoundingBox]: """ return getBounds(self, layer) - def getControlBounds(self, layer: GlyphSet) -> Optional[BoundingBox]: + def getControlBounds(self, layer: GlyphSet) -> BoundingBox | None: """Returns the (xMin, yMin, xMax, yMax) bounding box of the component, taking only the control points into account. diff --git a/src/ufoLib2/objects/contour.py b/src/ufoLib2/objects/contour.py index 710bb18f..69a4f490 100644 --- a/src/ufoLib2/objects/contour.py +++ b/src/ufoLib2/objects/contour.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import warnings from collections.abc import MutableSequence -from typing import Iterable, Iterator, List, Optional, Tuple, Union, overload +from typing import Iterable, Iterator, overload from attr import define, field from fontTools.pens.basePen import AbstractPen @@ -39,15 +41,15 @@ class Contour(MutableSequence): contour[0] = anotherPoint """ - points: List[Point] = field(factory=list) + points: list[Point] = field(factory=list) """The list of points in the contour.""" - identifier: Optional[str] = field(default=None, repr=False) + identifier: str | None = field(default=None, repr=False) """The globally unique identifier of the contour.""" # collections.abc.MutableSequence interface - def __delitem__(self, index: Union[int, slice]) -> None: + def __delitem__(self, index: int | slice) -> None: del self.points[index] @overload @@ -55,16 +57,14 @@ def __getitem__(self, index: int) -> Point: ... @overload - def __getitem__(self, index: slice) -> List[Point]: # noqa: F811 + def __getitem__(self, index: slice) -> list[Point]: # noqa: F811 ... - def __getitem__( # noqa: F811 - self, index: Union[int, slice] - ) -> Union[Point, List[Point]]: + def __getitem__(self, index: int | slice) -> Point | list[Point]: # noqa: F811 return self.points[index] def __setitem__( # noqa: F811 - self, index: Union[int, slice], point: Union[Point, Iterable[Point]] + self, index: int | slice, point: Point | Iterable[Point] ) -> None: if isinstance(index, int) and isinstance(point, Point): self.points[index] = point @@ -100,12 +100,12 @@ def open(self) -> bool: return True return self.points[0].type == "move" - def move(self, delta: Tuple[float, float]) -> None: + def move(self, delta: tuple[float, float]) -> None: """Moves contour by (x, y) font units.""" for point in self.points: point.move(delta) - def getBounds(self, layer: Optional[GlyphSet] = None) -> Optional[BoundingBox]: + def getBounds(self, layer: GlyphSet | None = None) -> BoundingBox | None: """Returns the (xMin, yMin, xMax, yMax) bounding box of the glyph, taking the actual contours into account. @@ -115,7 +115,7 @@ def getBounds(self, layer: Optional[GlyphSet] = None) -> Optional[BoundingBox]: return getBounds(self, layer) @property - def bounds(self) -> Optional[BoundingBox]: + def bounds(self) -> BoundingBox | None: """Returns the (xMin, yMin, xMax, yMax) bounding box of the glyph, taking the actual contours into account. @@ -123,9 +123,7 @@ def bounds(self) -> Optional[BoundingBox]: """ return self.getBounds() - def getControlBounds( - self, layer: Optional[GlyphSet] = None - ) -> Optional[BoundingBox]: + def getControlBounds(self, layer: GlyphSet | None = None) -> BoundingBox | None: """Returns the (xMin, yMin, xMax, yMax) bounding box of the glyph, taking only the control points into account. @@ -135,7 +133,7 @@ def getControlBounds( return getControlBounds(self, layer) @property - def controlPointBounds(self) -> Optional[BoundingBox]: + def controlPointBounds(self) -> BoundingBox | None: """Returns the (xMin, yMin, xMax, yMax) bounding box of the glyph, taking only the control points into account. diff --git a/src/ufoLib2/objects/dataSet.py b/src/ufoLib2/objects/dataSet.py index 86e3662b..492f5f51 100644 --- a/src/ufoLib2/objects/dataSet.py +++ b/src/ufoLib2/objects/dataSet.py @@ -1,4 +1,4 @@ -from typing import List +from __future__ import annotations from fontTools.ufoLib import UFOReader, UFOWriter @@ -25,7 +25,7 @@ class DataSet(DataStore): """ @staticmethod - def list_contents(reader: UFOReader) -> List[str]: + def list_contents(reader: UFOReader) -> list[str]: """Returns a list of POSIX filename strings in the data store.""" return reader.getDataDirectoryListing() diff --git a/src/ufoLib2/objects/features.py b/src/ufoLib2/objects/features.py index c5eec1de..d686ebd2 100644 --- a/src/ufoLib2/objects/features.py +++ b/src/ufoLib2/objects/features.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from attr import define diff --git a/src/ufoLib2/objects/font.py b/src/ufoLib2/objects/font.py index b9af1957..6d27f950 100644 --- a/src/ufoLib2/objects/font.py +++ b/src/ufoLib2/objects/font.py @@ -1,19 +1,8 @@ +from __future__ import annotations + import os import shutil -from typing import ( - Any, - Dict, - Iterable, - Iterator, - KeysView, - List, - Mapping, - MutableMapping, - Optional, - Tuple, - Union, - cast, -) +from typing import Any, Iterable, Iterator, KeysView, Mapping, MutableMapping, cast import attr import fs.base @@ -39,19 +28,19 @@ from ufoLib2.typing import HasIdentifier, PathLike, T -def _convert_Info(value: Union[Info, Mapping[str, Any]]) -> Info: +def _convert_Info(value: Info | Mapping[str, Any]) -> Info: return value if isinstance(value, Info) else Info(**value) -def _convert_DataSet(value: Union[DataSet, MutableMapping[str, bytes]]) -> DataSet: +def _convert_DataSet(value: DataSet | MutableMapping[str, bytes]) -> DataSet: return value if isinstance(value, DataSet) else DataSet(value) # type: ignore -def _convert_ImageSet(value: Union[ImageSet, MutableMapping[str, bytes]]) -> ImageSet: +def _convert_ImageSet(value: ImageSet | MutableMapping[str, bytes]) -> ImageSet: return value if isinstance(value, ImageSet) else ImageSet(value) # type: ignore -def _convert_Features(value: Union[Features, str]) -> Features: +def _convert_Features(value: Features | str) -> Features: return value if isinstance(value, Features) else Features(value) @@ -130,14 +119,14 @@ class Font: features: Features = field(factory=Features, converter=_convert_Features) """Features: The font Features object.""" - groups: Dict[str, List[str]] = field(factory=dict) + groups: dict[str, list[str]] = field(factory=dict) """Dict[str, List[str]]: A mapping of group names to a list of glyph names.""" - kerning: Dict[Tuple[str, str], float] = field(factory=dict) + kerning: dict[tuple[str, str], float] = field(factory=dict) """Dict[Tuple[str, str], float]: A mapping of a tuple of first and second kerning pair to a kerning value.""" - lib: Dict[str, Any] = field(factory=dict) + lib: dict[str, Any] = field(factory=dict) """Dict[str, Any]: A mapping of keys to arbitrary values.""" data: DataSet = field(factory=DataSet, converter=_convert_DataSet) @@ -147,17 +136,15 @@ class Font: """ImageSet: A mapping of image file paths to arbitrary image data.""" # init=False args, set by alternate open/read classmethod constructors - _path: Optional[PathLike] = field( + _path: PathLike | None = field( default=None, metadata=dict(copyable=False), eq=False, init=False ) - _lazy: Optional[bool] = field(default=None, init=False, eq=False) - _reader: Optional[UFOReader] = field(default=None, init=False, eq=False) - _fileStructure: Optional[UFOFileStructure] = field( - default=None, init=False, eq=False - ) + _lazy: bool | None = field(default=None, init=False, eq=False) + _reader: UFOReader | None = field(default=None, init=False, eq=False) + _fileStructure: UFOFileStructure | None = field(default=None, init=False, eq=False) @classmethod - def open(cls, path: PathLike, lazy: bool = True, validate: bool = True) -> "Font": + def open(cls, path: PathLike, lazy: bool = True, validate: bool = True) -> Font: """Instantiates a new Font object from a path to a UFO. Args: @@ -175,7 +162,7 @@ def open(cls, path: PathLike, lazy: bool = True, validate: bool = True) -> "Font return self @classmethod - def read(cls, reader: UFOReader, lazy: bool = True) -> "Font": + def read(cls, reader: UFOReader, lazy: bool = True) -> Font: """Instantiates a Font object from a :class:`fontTools.ufoLib.UFOReader`. Args: @@ -218,7 +205,7 @@ def __iter__(self) -> Iterator[Glyph]: def __len__(self) -> int: return len(self.layers.defaultLayer) - def get(self, name: str, default: Optional[T] = None) -> Union[Optional[T], Glyph]: + def get(self, name: str, default: T | None = None) -> T | Glyph | None: """Return the :class:`.Glyph` object for name if it is present in the default layer, otherwise return ``default``.""" return self.layers.defaultLayer.get(name, default) @@ -233,7 +220,7 @@ def close(self) -> None: if self._reader is not None: self._reader.close() - def __enter__(self) -> "Font": + def __enter__(self) -> Font: # TODO: Document an example for this. return self @@ -286,7 +273,7 @@ def __ne__(self, other: object) -> bool: return not result @property - def reader(self) -> Optional[UFOReader]: + def reader(self) -> UFOReader | None: """Returns the underlying :class:`fontTools.ufoLib.UFOReader`.""" return self._reader @@ -303,7 +290,7 @@ def unlazify(self) -> None: __deepcopy__ = _deepcopy_unlazify_attrs @property - def glyphOrder(self) -> List[str]: + def glyphOrder(self) -> list[str]: """The font's glyph order. See http://unifiedfontobject.org/versions/ufo3/lib.plist/#publicglyphorder for @@ -323,7 +310,7 @@ def glyphOrder(self) -> List[str]: return list(self.lib.get("public.glyphOrder", [])) @glyphOrder.setter - def glyphOrder(self, value: Optional[List[str]]) -> None: + def glyphOrder(self, value: list[str] | None) -> None: if value is None or len(value) == 0: if "public.glyphOrder" in self.lib: del self.lib["public.glyphOrder"] @@ -331,7 +318,7 @@ def glyphOrder(self, value: Optional[List[str]]) -> None: self.lib["public.glyphOrder"] = value @property - def guidelines(self) -> List[Guideline]: + def guidelines(self) -> list[Guideline]: """The font's global guidelines. Getter: @@ -346,12 +333,12 @@ def guidelines(self) -> List[Guideline]: return self.info.guidelines @guidelines.setter - def guidelines(self, value: Iterable[Union[Guideline, Mapping[str, Any]]]) -> None: + def guidelines(self, value: Iterable[Guideline | Mapping[str, Any]]) -> None: self.info.guidelines = [] for guideline in value: self.appendGuideline(guideline) - def objectLib(self, object: HasIdentifier) -> Dict[str, Any]: + def objectLib(self, object: HasIdentifier) -> dict[str, Any]: """Return the lib for an object with an identifier, as stored in a font's lib. If the object does not yet have an identifier, a new one is assigned to it. If @@ -373,12 +360,12 @@ def objectLib(self, object: HasIdentifier) -> Dict[str, Any]: return _object_lib(self.lib, object) @property - def path(self) -> Optional[PathLike]: + def path(self) -> PathLike | None: """Return the path of the UFO, if it was set, or None.""" return self._path @property - def bounds(self) -> Optional[BoundingBox]: + def bounds(self) -> BoundingBox | None: """Returns the (xMin, yMin, xMax, yMax) bounding box of the default layer, taking the actual contours into account. @@ -387,7 +374,7 @@ def bounds(self) -> Optional[BoundingBox]: return self.layers.defaultLayer.bounds @property - def controlPointBounds(self) -> Optional[BoundingBox]: + def controlPointBounds(self) -> BoundingBox | None: """Returns the (xMin, yMin, xMax, yMax) bounding box of the layer, taking only the control points into account. @@ -441,7 +428,7 @@ def renameLayer(self, name: str, newName: str, overwrite: bool = False) -> None: """ self.layers.renameLayer(name, newName, overwrite) - def appendGuideline(self, guideline: Union[Guideline, Mapping[str, Any]]) -> None: + def appendGuideline(self, guideline: Guideline | Mapping[str, Any]) -> None: """Appends a guideline to the list of the font's global guidelines. Creates the global guideline list unless it already exists. @@ -461,7 +448,7 @@ def appendGuideline(self, guideline: Union[Guideline, Mapping[str, Any]]) -> Non self.info.guidelines = [] self.info.guidelines.append(guideline) - def write(self, writer: UFOWriter, saveAs: Optional[bool] = None) -> None: + def write(self, writer: UFOWriter, saveAs: bool | None = None) -> None: """Writes this Font to a :class:`fontTools.ufoLib.UFOWriter`. Args: @@ -493,9 +480,9 @@ def write(self, writer: UFOWriter, saveAs: Optional[bool] = None) -> None: def save( self, - path: Optional[Union[PathLike, fs.base.FS]] = None, + path: PathLike | fs.base.FS | None = None, formatVersion: int = 3, - structure: Optional[UFOFileStructure] = None, + structure: UFOFileStructure | None = None, overwrite: bool = False, validate: bool = True, ) -> None: diff --git a/src/ufoLib2/objects/glyph.py b/src/ufoLib2/objects/glyph.py index a1775b46..750e1f66 100644 --- a/src/ufoLib2/objects/glyph.py +++ b/src/ufoLib2/objects/glyph.py @@ -1,5 +1,7 @@ +from __future__ import annotations + from copy import deepcopy -from typing import Any, Dict, Iterator, List, Mapping, Optional, Tuple, Union +from typing import Any, Iterator, Mapping from attr import define, field from fontTools.misc.transform import Transform @@ -50,7 +52,7 @@ class Glyph: :attr:`.Glyph.components` and :attr:`.Glyph.anchors` attributes. """ - _name: Optional[str] = None + _name: str | None = None width: float = 0 """The width of the glyph.""" @@ -58,26 +60,26 @@ class Glyph: height: float = 0 """The height of the glyph.""" - unicodes: List[int] = field(factory=list) + unicodes: list[int] = field(factory=list) """The Unicode code points assigned to the glyph. Note that a glyph can have multiple.""" _image: Image = field(factory=Image) - lib: Dict[str, Any] = field(factory=dict) + lib: dict[str, Any] = field(factory=dict) """The glyph's mapping of string keys to arbitrary data.""" - note: Optional[str] = None + note: str | None = None """A free form text note about the glyph.""" - _anchors: List[Anchor] = field(factory=list) - components: List[Component] = field(factory=list) + _anchors: list[Anchor] = field(factory=list) + components: list[Component] = field(factory=list) """The list of components the glyph contains.""" - contours: List[Contour] = field(factory=list) + contours: list[Contour] = field(factory=list) """The list of contours the glyph contains.""" - _guidelines: List[Guideline] = field(factory=list) + _guidelines: list[Guideline] = field(factory=list) def __len__(self) -> int: return len(self.contours) @@ -100,7 +102,7 @@ def __repr__(self) -> str: ) @property - def anchors(self) -> List[Anchor]: + def anchors(self) -> list[Anchor]: """The list of anchors the glyph contains. Getter: @@ -113,13 +115,13 @@ def anchors(self) -> List[Anchor]: return self._anchors @anchors.setter - def anchors(self, value: List[Anchor]) -> None: + def anchors(self, value: list[Anchor]) -> None: self.clearAnchors() for anchor in value: self.appendAnchor(anchor) @property - def guidelines(self) -> List[Guideline]: + def guidelines(self) -> list[Guideline]: """The list of guidelines the glyph contains. Getter: @@ -132,18 +134,18 @@ def guidelines(self) -> List[Guideline]: return self._guidelines @guidelines.setter - def guidelines(self, value: List[Guideline]) -> None: + def guidelines(self, value: list[Guideline]) -> None: self.clearGuidelines() for guideline in value: self.appendGuideline(guideline) @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """The name of the glyph.""" return self._name @property - def unicode(self) -> Optional[int]: + def unicode(self) -> int | None: """The first assigned Unicode code point or None. See http://unifiedfontobject.org/versions/ufo3/glyphs/glif/#unicode. @@ -158,7 +160,7 @@ def unicode(self) -> Optional[int]: return None @unicode.setter - def unicode(self, value: Optional[int]) -> None: + def unicode(self, value: int | None) -> None: if value is None: self.unicodes = [] else: @@ -185,7 +187,7 @@ def image(self) -> Image: return self._image @image.setter - def image(self, image: Optional[Union[Image, Mapping[str, Any]]]) -> None: + def image(self, image: Image | Mapping[str, Any] | None) -> None: if image is None: self._image.clear() elif isinstance(image, Image): @@ -204,7 +206,7 @@ def image(self, image: Optional[Union[Image, Mapping[str, Any]]]) -> None: color=image.get("color"), ) - def objectLib(self, object: HasIdentifier) -> Dict[str, Any]: + def objectLib(self, object: HasIdentifier) -> dict[str, Any]: """Return the lib for an object with an identifier, as stored in a glyph's lib. If the object does not yet have an identifier, a new one is assigned to it. If @@ -255,7 +257,7 @@ def removeComponent(self, component: Component) -> None: """Removes :class:`.Component` object from the glyph's list of components.""" self.components.remove(component) - def appendAnchor(self, anchor: Union[Anchor, Mapping[str, Any]]) -> None: + def appendAnchor(self, anchor: Anchor | Mapping[str, Any]) -> None: """Appends an :class:`.Anchor` object to glyph's list of anchors. Args: @@ -270,7 +272,7 @@ def appendAnchor(self, anchor: Union[Anchor, Mapping[str, Any]]) -> None: anchor = Anchor(**anchor) self.anchors.append(anchor) - def appendGuideline(self, guideline: Union[Guideline, Mapping[str, Any]]) -> None: + def appendGuideline(self, guideline: Guideline | Mapping[str, Any]) -> None: """Appends a :class:`.Guideline` object to glyph's list of guidelines. Args: @@ -292,7 +294,7 @@ def appendContour(self, contour: Contour) -> None: raise TypeError(f"Expected Contour, found {type(contour).__name__}") self.contours.append(contour) - def copy(self, name: Optional[str] = None) -> "Glyph": + def copy(self, name: str | None = None) -> Glyph: """Returns a new Glyph (deep) copy, optionally override the new glyph name.""" other = deepcopy(self) @@ -300,7 +302,7 @@ def copy(self, name: Optional[str] = None) -> "Glyph": other._name = name return other - def copyDataFromGlyph(self, glyph: "Glyph") -> None: + def copyDataFromGlyph(self, glyph: Glyph) -> None: """Deep-copies everything from the other glyph into self, except for the name. @@ -323,7 +325,7 @@ def copyDataFromGlyph(self, glyph: "Glyph") -> None: pointPen = self.getPointPen() glyph.drawPoints(pointPen) - def move(self, delta: Tuple[float, float]) -> None: + def move(self, delta: tuple[float, float]) -> None: """Moves all contours, components and anchors by (x, y) font units.""" for contour in self.contours: contour.move(delta) @@ -362,7 +364,7 @@ def getPointPen(self) -> AbstractPointPen: # lib wrapped attributes @property - def markColor(self) -> Optional[str]: + def markColor(self) -> str | None: """The color assigned to the glyph. See http://unifiedfontobject.org/versions/ufo3/glyphs/glif/#publicmarkcolor. @@ -377,14 +379,14 @@ def markColor(self) -> Optional[str]: return self.lib.get("public.markColor") @markColor.setter - def markColor(self, value: Optional[str]) -> None: + def markColor(self, value: str | None) -> None: if value is not None: self.lib["public.markColor"] = value elif "public.markColor" in self.lib: del self.lib["public.markColor"] @property - def verticalOrigin(self) -> Optional[float]: + def verticalOrigin(self) -> float | None: """The vertical origin of the glyph. See http://unifiedfontobject.org/versions/ufo3/glyphs/glif/#publicverticalorigin. @@ -399,7 +401,7 @@ def verticalOrigin(self) -> Optional[float]: return self.lib.get("public.verticalOrigin") @verticalOrigin.setter - def verticalOrigin(self, value: Optional[float]) -> None: + def verticalOrigin(self, value: float | None) -> None: if value is not None: self.lib["public.verticalOrigin"] = value elif "public.verticalOrigin" in self.lib: @@ -407,7 +409,7 @@ def verticalOrigin(self, value: Optional[float]) -> None: # bounds and side-bearings - def getBounds(self, layer: Optional[GlyphSet] = None) -> Optional[BoundingBox]: + def getBounds(self, layer: GlyphSet | None = None) -> BoundingBox | None: """Returns the (xMin, yMin, xMax, yMax) bounding box of the glyph, taking the actual contours into account. @@ -420,9 +422,7 @@ def getBounds(self, layer: Optional[GlyphSet] = None) -> Optional[BoundingBox]: return getBounds(self, layer) - def getControlBounds( - self, layer: Optional[GlyphSet] = None - ) -> Optional[BoundingBox]: + def getControlBounds(self, layer: GlyphSet | None = None) -> BoundingBox | None: """Returns the (xMin, yMin, xMax, yMax) bounding box of the glyph, taking only the control points into account. @@ -435,7 +435,7 @@ def getControlBounds( return getControlBounds(self, layer) - def getLeftMargin(self, layer: Optional[GlyphSet] = None) -> Optional[float]: + def getLeftMargin(self, layer: GlyphSet | None = None) -> float | None: """Returns the the space in font units from the point of origin to the left side of the glyph. @@ -448,7 +448,7 @@ def getLeftMargin(self, layer: Optional[GlyphSet] = None) -> Optional[float]: return None return bounds.xMin - def setLeftMargin(self, value: float, layer: Optional[GlyphSet] = None) -> None: + def setLeftMargin(self, value: float, layer: GlyphSet | None = None) -> None: """Sets the the space in font units from the point of origin to the left side of the glyph. @@ -465,7 +465,7 @@ def setLeftMargin(self, value: float, layer: Optional[GlyphSet] = None) -> None: self.width += diff self.move((diff, 0)) - def getRightMargin(self, layer: Optional[GlyphSet] = None) -> Optional[float]: + def getRightMargin(self, layer: GlyphSet | None = None) -> float | None: """Returns the the space in font units from the glyph's advance width to the right side of the glyph. @@ -478,7 +478,7 @@ def getRightMargin(self, layer: Optional[GlyphSet] = None) -> Optional[float]: return None return self.width - bounds.xMax - def setRightMargin(self, value: float, layer: Optional[GlyphSet] = None) -> None: + def setRightMargin(self, value: float, layer: GlyphSet | None = None) -> None: """Sets the the space in font units from the glyph's advance width to the right side of the glyph. @@ -492,7 +492,7 @@ def setRightMargin(self, value: float, layer: Optional[GlyphSet] = None) -> None return None self.width = bounds.xMax + value - def getBottomMargin(self, layer: Optional[GlyphSet] = None) -> Optional[float]: + def getBottomMargin(self, layer: GlyphSet | None = None) -> float | None: """Returns the the space in font units from the bottom of the canvas to the bottom of the glyph. @@ -508,7 +508,7 @@ def getBottomMargin(self, layer: Optional[GlyphSet] = None) -> Optional[float]: else: return bounds.yMin - (self.verticalOrigin - self.height) - def setBottomMargin(self, value: float, layer: Optional[GlyphSet] = None) -> None: + def setBottomMargin(self, value: float, layer: GlyphSet | None = None) -> None: """Sets the the space in font units from the bottom of the canvas to the bottom of the glyph. @@ -530,7 +530,7 @@ def setBottomMargin(self, value: float, layer: Optional[GlyphSet] = None) -> Non if diff: self.height += diff - def getTopMargin(self, layer: Optional[GlyphSet] = None) -> Optional[float]: + def getTopMargin(self, layer: GlyphSet | None = None) -> float | None: """Returns the the space in font units from the top of the canvas to the top of the glyph. @@ -546,7 +546,7 @@ def getTopMargin(self, layer: Optional[GlyphSet] = None) -> Optional[float]: else: return self.verticalOrigin - bounds.yMax - def setTopMargin(self, value: float, layer: Optional[GlyphSet] = None) -> None: + def setTopMargin(self, value: float, layer: GlyphSet | None = None) -> None: """Sets the the space in font units from the top of the canvas to the top of the glyph. diff --git a/src/ufoLib2/objects/guideline.py b/src/ufoLib2/objects/guideline.py index f54c72ff..9ac6bdf3 100644 --- a/src/ufoLib2/objects/guideline.py +++ b/src/ufoLib2/objects/guideline.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from attr import define @@ -13,22 +13,22 @@ class Guideline(AttrDictMixin): data composition restrictions. """ - x: Optional[float] = None + x: float | None = None """The origin x coordinate of the guideline.""" - y: Optional[float] = None + y: float | None = None """The origin y coordinate of the guideline.""" - angle: Optional[float] = None + angle: float | None = None """The angle of the guideline.""" - name: Optional[str] = None + name: str | None = None """The name of the guideline, no uniqueness required.""" - color: Optional[str] = None + color: str | None = None """The color of the guideline.""" - identifier: Optional[str] = None + identifier: str | None = None """The globally unique identifier of the guideline.""" def __attrs_post_init__(self) -> None: diff --git a/src/ufoLib2/objects/image.py b/src/ufoLib2/objects/image.py index efe5e427..8aa4ef97 100644 --- a/src/ufoLib2/objects/image.py +++ b/src/ufoLib2/objects/image.py @@ -1,5 +1,7 @@ +from __future__ import annotations + from collections.abc import Mapping -from typing import Any, Iterator, Optional, Tuple +from typing import Any, Iterator from attr import define, field from fontTools.misc.transform import Identity, Transform @@ -15,13 +17,13 @@ class Image(Mapping): http://unifiedfontobject.org/versions/ufo3/glyphs/glif/#image. """ - fileName: Optional[str] = None + fileName: str | None = None """The filename of the image.""" transformation: Transform = field(default=Identity, converter=_convert_transform) """The affine transformation applied to the image.""" - color: Optional[str] = None + color: str | None = None """The color applied to the image.""" def clear(self) -> None: @@ -38,7 +40,7 @@ def __bool__(self) -> bool: # the fontTools.ufoLib.validators.imageValidator requires that image is a # subclass of Mapping... - _transformation_keys_: Tuple[str, str, str, str, str, str] = ( + _transformation_keys_: tuple[str, str, str, str, str, str] = ( "xScale", "xyScale", "yxScale", @@ -46,7 +48,7 @@ def __bool__(self) -> bool: "xOffset", "yOffset", ) - _valid_keys_: Tuple[str, str, str, str, str, str, str, str] = ( + _valid_keys_: tuple[str, str, str, str, str, str, str, str] = ( "fileName", *_transformation_keys_, "color", diff --git a/src/ufoLib2/objects/imageSet.py b/src/ufoLib2/objects/imageSet.py index 299051fd..4710d160 100644 --- a/src/ufoLib2/objects/imageSet.py +++ b/src/ufoLib2/objects/imageSet.py @@ -1,4 +1,4 @@ -from typing import List +from __future__ import annotations from fontTools.ufoLib import UFOReader, UFOWriter @@ -27,7 +27,7 @@ class ImageSet(DataStore): """ @staticmethod - def list_contents(reader: UFOReader) -> List[str]: + def list_contents(reader: UFOReader) -> list[str]: """Returns a list of POSIX filename strings in the image data store.""" return reader.getImageDirectoryListing() diff --git a/src/ufoLib2/objects/info.py b/src/ufoLib2/objects/info.py index d6b6936f..b8ac8918 100644 --- a/src/ufoLib2/objects/info.py +++ b/src/ufoLib2/objects/info.py @@ -1,5 +1,7 @@ +from __future__ import annotations + from enum import IntEnum -from typing import Any, List, Optional, Sequence, Type, TypeVar, Union +from typing import Any, Sequence, TypeVar import attr from attr import define, field @@ -31,9 +33,7 @@ class GaspBehavior(IntEnum): SYMMETRIC_SMOOTHING = 3 -def _convert_GaspBehavior( - seq: Sequence[Union[GaspBehavior, int]] -) -> List[GaspBehavior]: +def _convert_GaspBehavior(seq: Sequence[GaspBehavior | int]) -> list[GaspBehavior]: return [v if isinstance(v, GaspBehavior) else GaspBehavior(v) for v in seq] @@ -41,7 +41,7 @@ def _convert_GaspBehavior( class GaspRangeRecord(AttrDictMixin): rangeMaxPPEM: int = field(validator=_positive) # Use Set[GaspBehavior] instead of List? - rangeGaspBehavior: List[GaspBehavior] = field(converter=_convert_GaspBehavior) + rangeGaspBehavior: list[GaspBehavior] = field(converter=_convert_GaspBehavior) @define @@ -70,8 +70,8 @@ class WidthClass(IntEnum): def _convert_optional_list( - lst: Optional[Sequence[Any]], klass: Type[Tc] -) -> Optional[List[Tc]]: + lst: Sequence[Any] | None, klass: type[Tc] +) -> list[Tc] | None: if lst is None: return None result = [] @@ -84,24 +84,24 @@ def _convert_optional_list( def _convert_guidelines( - values: Optional[Sequence[Union[Guideline, Any]]] -) -> Optional[List[Guideline]]: + values: Sequence[Guideline | Any] | None, +) -> list[Guideline] | None: return _convert_optional_list(values, Guideline) def _convert_gasp_range_records( - values: Optional[Sequence[Union[GaspRangeRecord, Any]]] -) -> Optional[List[GaspRangeRecord]]: + values: Sequence[GaspRangeRecord | Any] | None, +) -> list[GaspRangeRecord] | None: return _convert_optional_list(values, GaspRangeRecord) def _convert_name_records( - values: Optional[Sequence[Union[NameRecord, Any]]] -) -> Optional[List[NameRecord]]: + values: Sequence[NameRecord | Any] | None, +) -> list[NameRecord] | None: return _convert_optional_list(values, NameRecord) -def _convert_WidthClass(value: Optional[int]) -> Optional[WidthClass]: +def _convert_WidthClass(value: int | None) -> WidthClass | None: return None if value is None else WidthClass(value) @@ -114,173 +114,171 @@ class Info: mostly done during saving and loading. """ - familyName: Optional[str] = None - styleName: Optional[str] = None - styleMapFamilyName: Optional[str] = None - styleMapStyleName: Optional[str] = None - versionMajor: Optional[int] = field(default=None, validator=_optional_positive) - versionMinor: Optional[int] = field(default=None, validator=_optional_positive) + familyName: str | None = None + styleName: str | None = None + styleMapFamilyName: str | None = None + styleMapStyleName: str | None = None + versionMajor: int | None = field(default=None, validator=_optional_positive) + versionMinor: int | None = field(default=None, validator=_optional_positive) - copyright: Optional[str] = None - trademark: Optional[str] = None + copyright: str | None = None + trademark: str | None = None - unitsPerEm: Optional[float] = field(default=None, validator=_optional_positive) - descender: Optional[float] = None - xHeight: Optional[float] = None - capHeight: Optional[float] = None - ascender: Optional[float] = None - italicAngle: Optional[float] = None + unitsPerEm: float | None = field(default=None, validator=_optional_positive) + descender: float | None = None + xHeight: float | None = None + capHeight: float | None = None + ascender: float | None = None + italicAngle: float | None = None - note: Optional[str] = None + note: str | None = None - _guidelines: Optional[List[Guideline]] = field( + _guidelines: list[Guideline] | None = field( default=None, converter=_convert_guidelines ) @property - def guidelines(self) -> Optional[List[Guideline]]: + def guidelines(self) -> list[Guideline] | None: return self._guidelines @guidelines.setter - def guidelines(self, value: Optional[List[Guideline]]) -> None: + def guidelines(self, value: list[Guideline] | None) -> None: self._guidelines = _convert_guidelines(value) - _openTypeGaspRangeRecords: Optional[List[GaspRangeRecord]] = field( + _openTypeGaspRangeRecords: list[GaspRangeRecord] | None = field( default=None, converter=_convert_gasp_range_records ) @property - def openTypeGaspRangeRecords(self) -> Optional[List[GaspRangeRecord]]: + def openTypeGaspRangeRecords(self) -> list[GaspRangeRecord] | None: return self._openTypeGaspRangeRecords @openTypeGaspRangeRecords.setter - def openTypeGaspRangeRecords(self, value: Optional[List[GaspRangeRecord]]) -> None: + def openTypeGaspRangeRecords(self, value: list[GaspRangeRecord] | None) -> None: self._openTypeGaspRangeRecords = _convert_gasp_range_records(value) - openTypeHeadCreated: Optional[str] = None - openTypeHeadLowestRecPPEM: Optional[int] = field( + openTypeHeadCreated: str | None = None + openTypeHeadLowestRecPPEM: int | None = field( default=None, validator=_optional_positive ) - openTypeHeadFlags: Optional[List[int]] = None - - openTypeHheaAscender: Optional[int] = None - openTypeHheaDescender: Optional[int] = None - openTypeHheaLineGap: Optional[int] = None - openTypeHheaCaretSlopeRise: Optional[int] = None - openTypeHheaCaretSlopeRun: Optional[int] = None - openTypeHheaCaretOffset: Optional[int] = None - - openTypeNameDesigner: Optional[str] = None - openTypeNameDesignerURL: Optional[str] = None - openTypeNameManufacturer: Optional[str] = None - openTypeNameManufacturerURL: Optional[str] = None - openTypeNameLicense: Optional[str] = None - openTypeNameLicenseURL: Optional[str] = None - openTypeNameVersion: Optional[str] = None - openTypeNameUniqueID: Optional[str] = None - openTypeNameDescription: Optional[str] = None - openTypeNamePreferredFamilyName: Optional[str] = None - openTypeNamePreferredSubfamilyName: Optional[str] = None - openTypeNameCompatibleFullName: Optional[str] = None - openTypeNameSampleText: Optional[str] = None - openTypeNameWWSFamilyName: Optional[str] = None - openTypeNameWWSSubfamilyName: Optional[str] = None - - _openTypeNameRecords: Optional[List[NameRecord]] = field( + openTypeHeadFlags: list[int] | None = None + + openTypeHheaAscender: int | None = None + openTypeHheaDescender: int | None = None + openTypeHheaLineGap: int | None = None + openTypeHheaCaretSlopeRise: int | None = None + openTypeHheaCaretSlopeRun: int | None = None + openTypeHheaCaretOffset: int | None = None + + openTypeNameDesigner: str | None = None + openTypeNameDesignerURL: str | None = None + openTypeNameManufacturer: str | None = None + openTypeNameManufacturerURL: str | None = None + openTypeNameLicense: str | None = None + openTypeNameLicenseURL: str | None = None + openTypeNameVersion: str | None = None + openTypeNameUniqueID: str | None = None + openTypeNameDescription: str | None = None + openTypeNamePreferredFamilyName: str | None = None + openTypeNamePreferredSubfamilyName: str | None = None + openTypeNameCompatibleFullName: str | None = None + openTypeNameSampleText: str | None = None + openTypeNameWWSFamilyName: str | None = None + openTypeNameWWSSubfamilyName: str | None = None + + _openTypeNameRecords: list[NameRecord] | None = field( default=None, converter=_convert_name_records ) @property - def openTypeNameRecords(self) -> Optional[List[NameRecord]]: + def openTypeNameRecords(self) -> list[NameRecord] | None: return self._openTypeNameRecords @openTypeNameRecords.setter - def openTypeNameRecords(self, value: Optional[List[NameRecord]]) -> None: + def openTypeNameRecords(self, value: list[NameRecord] | None) -> None: self._openTypeNameRecords = _convert_name_records(value) - _openTypeOS2WidthClass: Optional[WidthClass] = field( + _openTypeOS2WidthClass: WidthClass | None = field( default=None, converter=_convert_WidthClass ) @property - def openTypeOS2WidthClass(self) -> Optional[WidthClass]: + def openTypeOS2WidthClass(self) -> WidthClass | None: return self._openTypeOS2WidthClass @openTypeOS2WidthClass.setter - def openTypeOS2WidthClass(self, value: Optional[WidthClass]) -> None: + def openTypeOS2WidthClass(self, value: WidthClass | None) -> None: self._openTypeOS2WidthClass = value if value is None else WidthClass(value) - openTypeOS2WeightClass: Optional[int] = field(default=None) + openTypeOS2WeightClass: int | None = field(default=None) @openTypeOS2WeightClass.validator - def _validate_weight_class(self, attribute: Any, value: Optional[int]) -> None: + def _validate_weight_class(self, attribute: Any, value: int | None) -> None: if value is not None and (value < 1 or value > 1000): raise ValueError("'openTypeOS2WeightClass' must be between 1 and 1000") - openTypeOS2Selection: Optional[List[int]] = None - openTypeOS2VendorID: Optional[str] = None - openTypeOS2Panose: Optional[List[int]] = None - openTypeOS2FamilyClass: Optional[List[int]] = None - openTypeOS2UnicodeRanges: Optional[List[int]] = None - openTypeOS2CodePageRanges: Optional[List[int]] = None - openTypeOS2TypoAscender: Optional[int] = None - openTypeOS2TypoDescender: Optional[int] = None - openTypeOS2TypoLineGap: Optional[int] = None - openTypeOS2WinAscent: Optional[int] = field( - default=None, validator=_optional_positive - ) - openTypeOS2WinDescent: Optional[int] = field( + openTypeOS2Selection: list[int] | None = None + openTypeOS2VendorID: str | None = None + openTypeOS2Panose: list[int] | None = None + openTypeOS2FamilyClass: list[int] | None = None + openTypeOS2UnicodeRanges: list[int] | None = None + openTypeOS2CodePageRanges: list[int] | None = None + openTypeOS2TypoAscender: int | None = None + openTypeOS2TypoDescender: int | None = None + openTypeOS2TypoLineGap: int | None = None + openTypeOS2WinAscent: int | None = field(default=None, validator=_optional_positive) + openTypeOS2WinDescent: int | None = field( default=None, validator=_optional_positive ) - openTypeOS2Type: Optional[List[int]] = None - openTypeOS2SubscriptXSize: Optional[int] = None - openTypeOS2SubscriptYSize: Optional[int] = None - openTypeOS2SubscriptXOffset: Optional[int] = None - openTypeOS2SubscriptYOffset: Optional[int] = None - openTypeOS2SuperscriptXSize: Optional[int] = None - openTypeOS2SuperscriptYSize: Optional[int] = None - openTypeOS2SuperscriptXOffset: Optional[int] = None - openTypeOS2SuperscriptYOffset: Optional[int] = None - openTypeOS2StrikeoutSize: Optional[int] = None - openTypeOS2StrikeoutPosition: Optional[int] = None - - openTypeVheaVertTypoAscender: Optional[int] = None - openTypeVheaVertTypoDescender: Optional[int] = None - openTypeVheaVertTypoLineGap: Optional[int] = None - openTypeVheaCaretSlopeRise: Optional[int] = None - openTypeVheaCaretSlopeRun: Optional[int] = None - openTypeVheaCaretOffset: Optional[int] = None - - postscriptFontName: Optional[str] = None - postscriptFullName: Optional[str] = None - postscriptSlantAngle: Optional[float] = None - postscriptUniqueID: Optional[int] = None - postscriptUnderlineThickness: Optional[float] = None - postscriptUnderlinePosition: Optional[float] = None - postscriptIsFixedPitch: Optional[bool] = None - postscriptBlueValues: Optional[List[float]] = None - postscriptOtherBlues: Optional[List[float]] = None - postscriptFamilyBlues: Optional[List[float]] = None - postscriptFamilyOtherBlues: Optional[List[float]] = None - postscriptStemSnapH: Optional[List[float]] = None - postscriptStemSnapV: Optional[List[float]] = None - postscriptBlueFuzz: Optional[float] = None - postscriptBlueShift: Optional[float] = None - postscriptBlueScale: Optional[float] = None - postscriptForceBold: Optional[bool] = None - postscriptDefaultWidthX: Optional[float] = None - postscriptNominalWidthX: Optional[float] = None - postscriptWeightName: Optional[str] = None - postscriptDefaultCharacter: Optional[str] = None - postscriptWindowsCharacterSet: Optional[str] = None + openTypeOS2Type: list[int] | None = None + openTypeOS2SubscriptXSize: int | None = None + openTypeOS2SubscriptYSize: int | None = None + openTypeOS2SubscriptXOffset: int | None = None + openTypeOS2SubscriptYOffset: int | None = None + openTypeOS2SuperscriptXSize: int | None = None + openTypeOS2SuperscriptYSize: int | None = None + openTypeOS2SuperscriptXOffset: int | None = None + openTypeOS2SuperscriptYOffset: int | None = None + openTypeOS2StrikeoutSize: int | None = None + openTypeOS2StrikeoutPosition: int | None = None + + openTypeVheaVertTypoAscender: int | None = None + openTypeVheaVertTypoDescender: int | None = None + openTypeVheaVertTypoLineGap: int | None = None + openTypeVheaCaretSlopeRise: int | None = None + openTypeVheaCaretSlopeRun: int | None = None + openTypeVheaCaretOffset: int | None = None + + postscriptFontName: str | None = None + postscriptFullName: str | None = None + postscriptSlantAngle: float | None = None + postscriptUniqueID: int | None = None + postscriptUnderlineThickness: float | None = None + postscriptUnderlinePosition: float | None = None + postscriptIsFixedPitch: bool | None = None + postscriptBlueValues: list[float] | None = None + postscriptOtherBlues: list[float] | None = None + postscriptFamilyBlues: list[float] | None = None + postscriptFamilyOtherBlues: list[float] | None = None + postscriptStemSnapH: list[float] | None = None + postscriptStemSnapV: list[float] | None = None + postscriptBlueFuzz: float | None = None + postscriptBlueShift: float | None = None + postscriptBlueScale: float | None = None + postscriptForceBold: bool | None = None + postscriptDefaultWidthX: float | None = None + postscriptNominalWidthX: float | None = None + postscriptWeightName: str | None = None + postscriptDefaultCharacter: str | None = None + postscriptWindowsCharacterSet: str | None = None # old stuff - macintoshFONDName: Optional[str] = None - macintoshFONDFamilyID: Optional[int] = None - year: Optional[int] = None + macintoshFONDName: str | None = None + macintoshFONDFamilyID: int | None = None + year: int | None = None @classmethod - def read(cls, reader: UFOReader) -> "Info": + def read(cls, reader: UFOReader) -> Info: """Instantiates a Info object from a :class:`fontTools.ufoLib.UFOReader`.""" self = cls() diff --git a/src/ufoLib2/objects/layer.py b/src/ufoLib2/objects/layer.py index 984bb8d6..b5320151 100644 --- a/src/ufoLib2/objects/layer.py +++ b/src/ufoLib2/objects/layer.py @@ -1,14 +1,6 @@ -from typing import ( - Any, - Dict, - Iterator, - KeysView, - Optional, - Sequence, - Set, - Union, - overload, -) +from __future__ import annotations + +from typing import Any, Iterator, KeysView, Sequence, overload from attr import define, field from fontTools.ufoLib.glifLib import GlyphSet @@ -27,9 +19,9 @@ def _convert_glyphs( - value: Union[Dict[str, Union[Glyph, Placeholder]], Sequence[Glyph]] -) -> Dict[str, Union[Glyph, Placeholder]]: - result: Dict[str, Union[Glyph, Placeholder]] = {} + value: dict[str, Glyph | Placeholder] | Sequence[Glyph] +) -> dict[str, Glyph | Placeholder]: + result: dict[str, Glyph | Placeholder] = {} glyph_ids = set() if isinstance(value, dict): for name, glyph in value.items(): @@ -103,19 +95,19 @@ class Layer: """ _name: str = DEFAULT_LAYER_NAME - _glyphs: Dict[str, Union[Glyph, Placeholder]] = field( + _glyphs: dict[str, Glyph | Placeholder] = field( factory=dict, converter=_convert_glyphs ) - color: Optional[str] = None + color: str | None = None """The color assigned to the layer.""" - lib: Dict[str, Any] = field(factory=dict) + lib: dict[str, Any] = field(factory=dict) """The layer's lib for mapping string keys to arbitrary data.""" _glyphSet: Any = field(default=None, init=False, eq=False) @classmethod - def read(cls, name: str, glyphSet: GlyphSet, lazy: bool = True) -> "Layer": + def read(cls, name: str, glyphSet: GlyphSet, lazy: bool = True) -> Layer: """Instantiates a Layer object from a :class:`fontTools.ufoLib.glifLib.GlyphSet`. @@ -126,7 +118,7 @@ def read(cls, name: str, glyphSet: GlyphSet, lazy: bool = True) -> "Layer": up front. """ glyphNames = glyphSet.keys() - glyphs: Dict[str, Union[Glyph, Placeholder]] + glyphs: dict[str, Glyph | Placeholder] if lazy: glyphs = {gn: _NOT_LOADED for gn in glyphNames} else: @@ -183,7 +175,7 @@ def __repr__(self) -> str: hex(id(self)), ) - def get(self, name: str, default: Optional[T] = None) -> Union[Optional[T], Glyph]: + def get(self, name: str, default: T | None = None) -> T | Glyph | None: """Return the Glyph object for name if it is present in this layer, otherwise return ``default``.""" try: @@ -200,10 +192,10 @@ def pop(self, key: str) -> Glyph: ... @overload - def pop(self, key: str, default: Union[Glyph, T] = ...) -> Union[Glyph, T]: + def pop(self, key: str, default: Glyph | T = ...) -> Glyph | T: ... - def pop(self, key: str, default: Union[Glyph, T] = KeyError) -> Union[Glyph, T]: # type: ignore + def pop(self, key: str, default: Glyph | T = KeyError) -> Glyph | T: # type: ignore """Remove and return glyph from layer. Args: @@ -227,7 +219,7 @@ def name(self) -> str: return self._name @property - def bounds(self) -> Optional[BoundingBox]: + def bounds(self) -> BoundingBox | None: """Returns the (xMin, yMin, xMax, yMax) bounding box of the layer, taking the actual contours into account. @@ -239,7 +231,7 @@ def bounds(self) -> Optional[BoundingBox]: return bounds @property - def controlPointBounds(self) -> Optional[BoundingBox]: + def controlPointBounds(self) -> BoundingBox | None: """Returns the (xMin, yMin, xMax, yMax) bounding box of the layer, taking only the control points into account. @@ -258,7 +250,7 @@ def addGlyph(self, glyph: Glyph) -> None: def insertGlyph( self, glyph: Glyph, - name: Optional[str] = None, + name: str | None = None, overwrite: bool = True, copy: bool = True, ) -> None: @@ -353,7 +345,7 @@ def write(self, glyphSet: GlyphSet, saveAs: bool = True) -> None: self._glyphSet = None -def _fetch_glyph_identifiers(glyph: Glyph) -> Set[str]: +def _fetch_glyph_identifiers(glyph: Glyph) -> set[str]: """Returns all identifiers in use in a glyph.""" identifiers = set() diff --git a/src/ufoLib2/objects/layerSet.py b/src/ufoLib2/objects/layerSet.py index f0046606..b2e4c017 100644 --- a/src/ufoLib2/objects/layerSet.py +++ b/src/ufoLib2/objects/layerSet.py @@ -1,5 +1,7 @@ +from __future__ import annotations + from collections import OrderedDict -from typing import AbstractSet, Any, Iterable, Iterator, List, Optional, Sized, Union +from typing import AbstractSet, Any, Iterable, Iterator, Sized from attr import define, field from fontTools.ufoLib import UFOReader, UFOWriter @@ -51,14 +53,14 @@ class LayerSet: del font.layers["myLayerName"] """ - _layers: "OrderedDict[str, Union[Layer, Placeholder]]" = field( + _layers: OrderedDict[str, Layer | Placeholder] = field( validator=_must_have_at_least_one_item, ) defaultLayer: Layer """The Layer that is marked as the default, typically named ``public.default``.""" - _reader: Optional[UFOReader] = field(default=None, init=False, eq=False) + _reader: UFOReader | None = field(default=None, init=False, eq=False) def __attrs_post_init__(self) -> None: if not any(layer is self.defaultLayer for layer in self._layers.values()): @@ -67,21 +69,21 @@ def __attrs_post_init__(self) -> None: ) @classmethod - def default(cls) -> "LayerSet": + def default(cls) -> LayerSet: """Return a new LayerSet with an empty default Layer.""" return cls.from_iterable([Layer()]) @classmethod def from_iterable( cls, value: Iterable[Layer], defaultLayerName: str = DEFAULT_LAYER_NAME - ) -> "LayerSet": + ) -> LayerSet: """Instantiates a LayerSet from an iterable of :class:`.Layer` objects. Args: value: an iterable of :class:`.Layer` objects. defaultLayerName: the name of the default layer of the ones in ``value``. """ - layers: OrderedDict[str, Union[Layer, Placeholder]] = OrderedDict() + layers: OrderedDict[str, Layer | Placeholder] = OrderedDict() defaultLayer = None for layer in value: if not isinstance(layer, Layer): @@ -99,7 +101,7 @@ def from_iterable( return cls(layers=layers, defaultLayer=defaultLayer) @classmethod - def read(cls, reader: UFOReader, lazy: bool = True) -> "LayerSet": + def read(cls, reader: UFOReader, lazy: bool = True) -> LayerSet: """Instantiates a LayerSet object from a :class:`fontTools.ufoLib.UFOReader`. Args: @@ -107,7 +109,7 @@ def read(cls, reader: UFOReader, lazy: bool = True) -> "LayerSet": lazy: If True, load glyphs, data files and images as they are accessed. If False, load everything up front. """ - layers: OrderedDict[str, Union[Layer, Placeholder]] = OrderedDict() + layers: OrderedDict[str, Layer | Placeholder] = OrderedDict() defaultLayer = None defaultLayerName = reader.getDefaultLayerName() @@ -177,7 +179,7 @@ def __iter__(self) -> Iterator[Layer]: def __len__(self) -> int: return len(self._layers) - def get(self, name: str, default: Optional[T] = None) -> Union[Optional[T], Layer]: + def get(self, name: str, default: T | None = None) -> T | Layer | None: try: return self[name] except KeyError: @@ -197,7 +199,7 @@ def __repr__(self) -> str: ) @property - def layerOrder(self) -> List[str]: + def layerOrder(self) -> list[str]: """The font's layer order. Getter: @@ -214,7 +216,7 @@ def layerOrder(self) -> List[str]: return list(self._layers) @layerOrder.setter - def layerOrder(self, order: List[str]) -> None: + def layerOrder(self, order: list[str]) -> None: if set(order) != set(self._layers): raise Error( "`order` must contain the same layers that are currently present." @@ -282,7 +284,7 @@ def renameLayer(self, name: str, newName: str, overwrite: bool = False) -> None: self._layers[newName] = layer layer._name = newName - def write(self, writer: UFOWriter, saveAs: Optional[bool] = None) -> None: + def write(self, writer: UFOWriter, saveAs: bool | None = None) -> None: """Writes this LayerSet to a :class:`fontTools.ufoLib.UFOWriter`. Args: diff --git a/src/ufoLib2/objects/misc.py b/src/ufoLib2/objects/misc.py index 1c160e27..18280ed9 100644 --- a/src/ufoLib2/objects/misc.py +++ b/src/ufoLib2/objects/misc.py @@ -1,22 +1,11 @@ +from __future__ import annotations + import collections.abc import uuid from abc import abstractmethod from collections.abc import Mapping, MutableMapping from copy import deepcopy -from typing import ( - Any, - Dict, - Iterator, - List, - NamedTuple, - Optional, - Sequence, - Set, - Type, - TypeVar, - Union, - cast, -) +from typing import Any, Iterator, NamedTuple, Sequence, TypeVar, cast import attr from attr import define, field @@ -38,7 +27,7 @@ class BoundingBox(NamedTuple): yMax: float -def getBounds(drawable: Drawable, layer: Optional[GlyphSet]) -> Optional[BoundingBox]: +def getBounds(drawable: Drawable, layer: GlyphSet | None) -> BoundingBox | None: pen = BoundsPen(layer) # raise 'KeyError' when a referenced component is missing from glyph set pen.skipMissingComponents = False @@ -46,9 +35,7 @@ def getBounds(drawable: Drawable, layer: Optional[GlyphSet]) -> Optional[Boundin return None if pen.bounds is None else BoundingBox(*pen.bounds) -def getControlBounds( - drawable: Drawable, layer: Optional[GlyphSet] -) -> Optional[BoundingBox]: +def getControlBounds(drawable: Drawable, layer: GlyphSet | None) -> BoundingBox | None: pen = ControlBoundsPen(layer) # raise 'KeyError' when a referenced component is missing from glyph set pen.skipMissingComponents = False @@ -57,8 +44,8 @@ def getControlBounds( def unionBounds( - bounds1: Optional[BoundingBox], bounds2: Optional[BoundingBox] -) -> Optional[BoundingBox]: + bounds1: BoundingBox | None, bounds2: BoundingBox | None +) -> BoundingBox | None: if bounds1 is None: return bounds2 if bounds2 is None: @@ -80,14 +67,14 @@ def _deepcopy_unlazify_attrs(self: Any, memo: Any) -> Any: ) -def _object_lib(parent_lib: Dict[str, Any], object: HasIdentifier) -> Dict[str, Any]: +def _object_lib(parent_lib: dict[str, Any], object: HasIdentifier) -> dict[str, Any]: if object.identifier is None: # Use UUID4 because it allows us to set a new identifier without # checking if it's already used anywhere else and be right most # of the time. object.identifier = str(uuid.uuid4()) - object_libs: Dict[str, Any] + object_libs: dict[str, Any] if "public.objectLibs" not in parent_lib: object_libs = parent_lib["public.objectLibs"] = {} else: @@ -100,7 +87,7 @@ def _object_lib(parent_lib: Dict[str, Any], object: HasIdentifier) -> Dict[str, return lib -def _prune_object_libs(parent_lib: Dict[str, Any], identifiers: Set[str]) -> None: +def _prune_object_libs(parent_lib: dict[str, Any], identifiers: set[str]) -> None: """Prune non-existing objects and empty libs from a lib's public.objectLibs. @@ -135,11 +122,11 @@ class DataStore(MutableMapping): differ in which reader and writer methods they call. """ - _data: Dict[str, Union[bytes, Placeholder]] = field(factory=dict) + _data: dict[str, bytes | Placeholder] = field(factory=dict) - _lazy: Optional[bool] = field(default=False, kw_only=True, eq=False, init=False) - _reader: Optional[UFOReader] = field(default=None, init=False, repr=False, eq=False) - _scheduledForDeletion: Set[str] = field( + _lazy: bool | None = field(default=False, kw_only=True, eq=False, init=False) + _reader: UFOReader | None = field(default=None, init=False, repr=False, eq=False) + _scheduledForDeletion: set[str] = field( factory=set, init=False, repr=False, eq=False ) @@ -165,7 +152,7 @@ def __ne__(self, other: object) -> bool: return not result @classmethod - def read(cls: Type[Tds], reader: UFOReader, lazy: bool = True) -> Tds: + def read(cls: type[Tds], reader: UFOReader, lazy: bool = True) -> Tds: """Instantiate the data store from a :class:`fontTools.ufoLib.UFOReader`.""" self = cls() for fileName in cls.list_contents(reader): @@ -180,7 +167,7 @@ def read(cls: Type[Tds], reader: UFOReader, lazy: bool = True) -> Tds: @staticmethod @abstractmethod - def list_contents(reader: UFOReader) -> List[str]: + def list_contents(reader: UFOReader) -> list[str]: """Returns a list of POSIX filename strings in the data store.""" ... @@ -245,7 +232,7 @@ def __repr__(self) -> str: hex(id(self)), ) - def write(self, writer: UFOWriter, saveAs: Optional[bool] = None) -> None: + def write(self, writer: UFOWriter, saveAs: bool | None = None) -> None: """Write the data store to a :class:`fontTools.ufoLib.UFOWriter`.""" if saveAs is None: saveAs = self._reader is not writer @@ -273,7 +260,7 @@ def write(self, writer: UFOWriter, saveAs: Optional[bool] = None) -> None: self._reader = None @property - def fileNames(self) -> List[str]: + def fileNames(self) -> list[str]: """Returns a list of filenames in the data store.""" return list(self._data.keys()) @@ -302,7 +289,7 @@ def __len__(self) -> int: return sum(1 for _ in self) -def _convert_transform(t: Union[Transform, Sequence[float]]) -> Transform: +def _convert_transform(t: Transform | Sequence[float]) -> Transform: """Return a passed-in Transform as is, otherwise convert a sequence of numbers to a Transform if need be.""" return t if isinstance(t, Transform) else Transform(*t) diff --git a/src/ufoLib2/objects/point.py b/src/ufoLib2/objects/point.py index cce89a2a..d6e8864c 100644 --- a/src/ufoLib2/objects/point.py +++ b/src/ufoLib2/objects/point.py @@ -1,4 +1,4 @@ -from typing import Optional, Tuple +from __future__ import annotations from attr import define @@ -16,7 +16,7 @@ class Point: y: float """The y coordinate of the point.""" - type: Optional[str] = None + type: str | None = None """The type of the point. ``None`` means "offcurve". @@ -27,23 +27,23 @@ class Point: smooth: bool = False """Whether a smooth curvature should be maintained at this point.""" - name: Optional[str] = None + name: str | None = None """The name of the point, no uniqueness required.""" - identifier: Optional[str] = None + identifier: str | None = None """The globally unique identifier of the point.""" # XXX: Add post_init to check spec-mandated invariants? @property - def segmentType(self) -> Optional[str]: + def segmentType(self) -> str | None: """Returns the type of the point. |defcon_compat| """ return self.type - def move(self, delta: Tuple[float, float]) -> None: + def move(self, delta: tuple[float, float]) -> None: """Moves point by (x, y) font units.""" x, y = delta self.x += x diff --git a/src/ufoLib2/pointPens/glyphPointPen.py b/src/ufoLib2/pointPens/glyphPointPen.py index 39b7efcf..7cf959d2 100644 --- a/src/ufoLib2/pointPens/glyphPointPen.py +++ b/src/ufoLib2/pointPens/glyphPointPen.py @@ -1,4 +1,6 @@ -from typing import TYPE_CHECKING, Any, Optional, Tuple +from __future__ import annotations + +from typing import TYPE_CHECKING, Any from fontTools.misc.transform import Transform from fontTools.pens.pointPen import AbstractPointPen @@ -20,11 +22,11 @@ class GlyphPointPen(AbstractPointPen): __slots__ = "_glyph", "_contour" - def __init__(self, glyph: "Glyph") -> None: - self._glyph: "Glyph" = glyph - self._contour: Optional[Contour] = None + def __init__(self, glyph: Glyph) -> None: + self._glyph: Glyph = glyph + self._contour: Contour | None = None - def beginPath(self, identifier: Optional[str] = None, **kwargs: Any) -> None: + def beginPath(self, identifier: str | None = None, **kwargs: Any) -> None: self._contour = Contour(identifier=identifier) def endPath(self) -> None: @@ -35,11 +37,11 @@ def endPath(self) -> None: def addPoint( self, - pt: Tuple[float, float], - segmentType: Optional[str] = None, + pt: tuple[float, float], + segmentType: str | None = None, smooth: bool = False, - name: Optional[str] = None, - identifier: Optional[str] = None, + name: str | None = None, + identifier: str | None = None, **kwargs: Any, ) -> None: if self._contour is None: @@ -55,7 +57,7 @@ def addComponent( self, baseGlyph: str, transformation: Transform, - identifier: Optional[str] = None, + identifier: str | None = None, **kwargs: Any, ) -> None: component = Component(baseGlyph, transformation, identifier=identifier) diff --git a/src/ufoLib2/typing.py b/src/ufoLib2/typing.py index c66d5e44..8b8eb6db 100644 --- a/src/ufoLib2/typing.py +++ b/src/ufoLib2/typing.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import os import sys -from typing import Optional, TypeVar, Union +from typing import TypeVar, Union from fontTools.pens.basePen import AbstractPen from fontTools.pens.pointPen import AbstractPointPen @@ -42,7 +44,7 @@ class HasIdentifier(Protocol): """Any object that has a unique identifier in some context that can be used as a key in a public.objectLibs dictionary.""" - identifier: Optional[str] + identifier: str | None class GlyphSet(Protocol): @@ -62,5 +64,5 @@ class GlyphSet(Protocol): def __contains__(self, name: object) -> bool: ... - def __getitem__(self, name: str) -> Union[Drawable, DrawablePoints]: + def __getitem__(self, name: str) -> Drawable | DrawablePoints: ... diff --git a/tests/conftest.py b/tests/conftest.py index ea050d30..18701f5d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pathlib import shutil diff --git a/tests/objects/test_component.py b/tests/objects/test_component.py index 25e072ad..328ef815 100644 --- a/tests/objects/test_component.py +++ b/tests/objects/test_component.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from ufoLib2.objects import Component, Glyph, Layer diff --git a/tests/objects/test_contour.py b/tests/objects/test_contour.py index dabff7ee..4105931f 100644 --- a/tests/objects/test_contour.py +++ b/tests/objects/test_contour.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from ufoLib2.objects import Glyph diff --git a/tests/objects/test_datastore.py b/tests/objects/test_datastore.py index 0e769786..0a17e049 100644 --- a/tests/objects/test_datastore.py +++ b/tests/objects/test_datastore.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path import pytest diff --git a/tests/objects/test_font.py b/tests/objects/test_font.py index b924b134..f3f91715 100644 --- a/tests/objects/test_font.py +++ b/tests/objects/test_font.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from ufoLib2.objects import Font, Glyph, Guideline diff --git a/tests/objects/test_glyph.py b/tests/objects/test_glyph.py index b116e418..61528b41 100644 --- a/tests/objects/test_glyph.py +++ b/tests/objects/test_glyph.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path import pytest diff --git a/tests/objects/test_layer.py b/tests/objects/test_layer.py index 51e09a39..061d2330 100644 --- a/tests/objects/test_layer.py +++ b/tests/objects/test_layer.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from ufoLib2.objects import Glyph, Layer diff --git a/tests/objects/test_object_lib.py b/tests/objects/test_object_lib.py index 8ffe1077..d3998a7b 100644 --- a/tests/objects/test_object_lib.py +++ b/tests/objects/test_object_lib.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from ufoLib2.objects import Anchor, Font, Guideline diff --git a/tests/test_ufoLib2.py b/tests/test_ufoLib2.py index beae0064..d18d81ce 100644 --- a/tests/test_ufoLib2.py +++ b/tests/test_ufoLib2.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from collections import OrderedDict from copy import deepcopy From dd881a02362a1b25c0e81bf858b0c496015f9e37 Mon Sep 17 00:00:00 2001 From: Nikolaus Waxweiler Date: Tue, 9 Nov 2021 14:50:24 +0000 Subject: [PATCH 2/3] Make codebase pass mypy's strict mode Also ignore not-yet-installed _version module for clean-slate linting. --- mypy.ini | 4 +++ src/ufoLib2/__init__.py | 2 +- src/ufoLib2/objects/contour.py | 2 +- src/ufoLib2/objects/dataSet.py | 4 +-- src/ufoLib2/objects/image.py | 2 +- src/ufoLib2/objects/imageSet.py | 4 +-- src/ufoLib2/objects/layerSet.py | 12 +++---- src/ufoLib2/objects/misc.py | 18 +++++----- src/ufoLib2/pointPens/glyphPointPen.py | 2 +- tests/conftest.py | 9 ++--- tests/objects/test_component.py | 8 ++--- tests/objects/test_contour.py | 7 ++-- tests/objects/test_font.py | 23 ++++++++---- tests/objects/test_glyph.py | 48 +++++++++++++------------- tests/objects/test_layer.py | 16 ++++----- tests/objects/test_object_lib.py | 7 ++-- tests/test_ufoLib2.py | 46 ++++++++++++------------ tox.ini | 2 +- 18 files changed, 117 insertions(+), 99 deletions(-) diff --git a/mypy.ini b/mypy.ini index 1d466125..bdf32c23 100644 --- a/mypy.ini +++ b/mypy.ini @@ -19,6 +19,10 @@ strict_equality = True [mypy-ufoLib2.*] disallow_untyped_defs = True +[mypy-ufoLib2._version] +# Unavailable until package installation. +ignore_missing_imports = True + [mypy-fontTools.*] ignore_missing_imports = True diff --git a/src/ufoLib2/__init__.py b/src/ufoLib2/__init__.py index fdf09ce5..15a47c97 100644 --- a/src/ufoLib2/__init__.py +++ b/src/ufoLib2/__init__.py @@ -5,7 +5,7 @@ from ufoLib2.objects import Font try: - from ._version import version as __version__ # type: ignore + from ._version import version as __version__ except ImportError: __version__ = "0.0.0+unknown" diff --git a/src/ufoLib2/objects/contour.py b/src/ufoLib2/objects/contour.py index 69a4f490..88121293 100644 --- a/src/ufoLib2/objects/contour.py +++ b/src/ufoLib2/objects/contour.py @@ -14,7 +14,7 @@ @define -class Contour(MutableSequence): +class Contour(MutableSequence[Point]): """Represents a contour as a list of points. Behavior: diff --git a/src/ufoLib2/objects/dataSet.py b/src/ufoLib2/objects/dataSet.py index 492f5f51..f1a3e9c2 100644 --- a/src/ufoLib2/objects/dataSet.py +++ b/src/ufoLib2/objects/dataSet.py @@ -27,12 +27,12 @@ class DataSet(DataStore): @staticmethod def list_contents(reader: UFOReader) -> list[str]: """Returns a list of POSIX filename strings in the data store.""" - return reader.getDataDirectoryListing() + return reader.getDataDirectoryListing() # type: ignore @staticmethod def read_data(reader: UFOReader, filename: str) -> bytes: """Returns the data at filename within the store.""" - return reader.readData(filename) + return reader.readData(filename) # type: ignore @staticmethod def write_data(writer: UFOWriter, filename: str, data: bytes) -> None: diff --git a/src/ufoLib2/objects/image.py b/src/ufoLib2/objects/image.py index 8aa4ef97..fbf376d2 100644 --- a/src/ufoLib2/objects/image.py +++ b/src/ufoLib2/objects/image.py @@ -10,7 +10,7 @@ @define -class Image(Mapping): +class Image(Mapping[str, Any]): """Represents a background image reference. See http://unifiedfontobject.org/versions/ufo3/images/ and diff --git a/src/ufoLib2/objects/imageSet.py b/src/ufoLib2/objects/imageSet.py index 4710d160..ffc1380b 100644 --- a/src/ufoLib2/objects/imageSet.py +++ b/src/ufoLib2/objects/imageSet.py @@ -29,12 +29,12 @@ class ImageSet(DataStore): @staticmethod def list_contents(reader: UFOReader) -> list[str]: """Returns a list of POSIX filename strings in the image data store.""" - return reader.getImageDirectoryListing() + return reader.getImageDirectoryListing() # type: ignore @staticmethod def read_data(reader: UFOReader, filename: str) -> bytes: """Returns the image data at filename within the store.""" - return reader.readImage(filename) + return reader.readImage(filename) # type: ignore @staticmethod def write_data(writer: UFOWriter, filename: str, data: bytes) -> None: diff --git a/src/ufoLib2/objects/layerSet.py b/src/ufoLib2/objects/layerSet.py index b2e4c017..b9f2de3f 100644 --- a/src/ufoLib2/objects/layerSet.py +++ b/src/ufoLib2/objects/layerSet.py @@ -1,6 +1,5 @@ from __future__ import annotations -from collections import OrderedDict from typing import AbstractSet, Any, Iterable, Iterator, Sized from attr import define, field @@ -53,7 +52,7 @@ class LayerSet: del font.layers["myLayerName"] """ - _layers: OrderedDict[str, Layer | Placeholder] = field( + _layers: dict[str, Layer | Placeholder] = field( validator=_must_have_at_least_one_item, ) @@ -83,7 +82,7 @@ def from_iterable( value: an iterable of :class:`.Layer` objects. defaultLayerName: the name of the default layer of the ones in ``value``. """ - layers: OrderedDict[str, Layer | Placeholder] = OrderedDict() + layers: dict[str, Layer | Placeholder] = {} defaultLayer = None for layer in value: if not isinstance(layer, Layer): @@ -109,7 +108,7 @@ def read(cls, reader: UFOReader, lazy: bool = True) -> LayerSet: lazy: If True, load glyphs, data files and images as they are accessed. If False, load everything up front. """ - layers: OrderedDict[str, Layer | Placeholder] = OrderedDict() + layers: dict[str, Layer | Placeholder] = {} defaultLayer = None defaultLayerName = reader.getDefaultLayerName() @@ -221,10 +220,7 @@ def layerOrder(self, order: list[str]) -> None: raise Error( "`order` must contain the same layers that are currently present." ) - layers = OrderedDict() - for name in order: - layers[name] = self._layers[name] - self._layers = layers + self._layers = {name: self._layers[name] for name in order} def newLayer(self, name: str, **kwargs: Any) -> Layer: """Creates and returns a named layer. diff --git a/src/ufoLib2/objects/misc.py b/src/ufoLib2/objects/misc.py index 18280ed9..68195fd0 100644 --- a/src/ufoLib2/objects/misc.py +++ b/src/ufoLib2/objects/misc.py @@ -67,12 +67,12 @@ def _deepcopy_unlazify_attrs(self: Any, memo: Any) -> Any: ) -def _object_lib(parent_lib: dict[str, Any], object: HasIdentifier) -> dict[str, Any]: - if object.identifier is None: +def _object_lib(parent_lib: dict[str, Any], obj: HasIdentifier) -> dict[str, Any]: + if obj.identifier is None: # Use UUID4 because it allows us to set a new identifier without # checking if it's already used anywhere else and be right most # of the time. - object.identifier = str(uuid.uuid4()) + obj.identifier = str(uuid.uuid4()) object_libs: dict[str, Any] if "public.objectLibs" not in parent_lib: @@ -81,9 +81,11 @@ def _object_lib(parent_lib: dict[str, Any], object: HasIdentifier) -> dict[str, object_libs = parent_lib["public.objectLibs"] assert isinstance(object_libs, collections.abc.MutableMapping) - if object.identifier in object_libs: - return object_libs[object.identifier] - lib = object_libs[object.identifier] = {} + if obj.identifier in object_libs: + object_lib: dict[str, Any] = object_libs[obj.identifier] + return object_lib + lib: dict[str, Any] = {} + object_libs[obj.identifier] = lib return lib @@ -115,7 +117,7 @@ class Placeholder: @define -class DataStore(MutableMapping): +class DataStore(MutableMapping[str, bytes]): """Represents the base class for ImageSet and DataSet. Both behave like a dictionary that loads its "values" lazily by default and only @@ -265,7 +267,7 @@ def fileNames(self) -> list[str]: return list(self._data.keys()) -class AttrDictMixin(Mapping): +class AttrDictMixin(Mapping[str, Any]): """Read attribute values using mapping interface. For use with Anchors and Guidelines classes, where client code diff --git a/src/ufoLib2/pointPens/glyphPointPen.py b/src/ufoLib2/pointPens/glyphPointPen.py index 7cf959d2..ff3fb009 100644 --- a/src/ufoLib2/pointPens/glyphPointPen.py +++ b/src/ufoLib2/pointPens/glyphPointPen.py @@ -13,7 +13,7 @@ from ufoLib2.objects.glyph import Glyph -class GlyphPointPen(AbstractPointPen): +class GlyphPointPen(AbstractPointPen): # type: ignore """A point pen. See :mod:`fontTools.pens.basePen` and :mod:`fontTools.pens.pointPen` for an diff --git a/tests/conftest.py b/tests/conftest.py index 18701f5d..6545cf1d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,8 @@ from __future__ import annotations -import pathlib import shutil +from pathlib import Path +from typing import Any import pytest @@ -9,12 +10,12 @@ @pytest.fixture -def datadir(request): - return pathlib.Path(__file__).parent / "data" +def datadir(request: Any) -> Path: + return Path(__file__).parent / "data" @pytest.fixture -def ufo_UbuTestData(tmp_path, datadir): +def ufo_UbuTestData(tmp_path: Path, datadir: Path) -> ufoLib2.Font: ufo_path = tmp_path / "UbuTestData.ufo" shutil.copytree(datadir / "UbuTestData.ufo", ufo_path) return ufoLib2.Font.open(ufo_path) diff --git a/tests/objects/test_component.py b/tests/objects/test_component.py index 328ef815..36155058 100644 --- a/tests/objects/test_component.py +++ b/tests/objects/test_component.py @@ -6,7 +6,7 @@ @pytest.fixture -def layer(): +def layer() -> Layer: a = Glyph("a") pen = a.getPen() pen.moveTo((0, 0)) @@ -17,19 +17,19 @@ def layer(): return layer -def test_component_getBounds(layer): +def test_component_getBounds(layer: Layer) -> None: assert Component("a", (1, 0, 0, 1, 0, 0)).getBounds(layer) == (0, 0, 7.5, 20) assert Component("a", (1, 0, 0, 1, -5, 0)).getBounds(layer) == (-5, 0, 2.5, 20) assert Component("a", (1, 0, 0, 1, 0, 5)).getBounds(layer) == (0, 5, 7.5, 25) -def test_component_getControlBounds(layer): +def test_component_getControlBounds(layer: Layer) -> None: assert Component("a", (1, 0, 0, 1, 0, 0)).getControlBounds(layer) == (0, 0, 10, 20) assert Component("a", (1, 0, 0, 1, -5, 0)).getControlBounds(layer) == (-5, 0, 5, 20) assert Component("a", (1, 0, 0, 1, 0, 5)).getControlBounds(layer) == (0, 5, 10, 25) -def test_component_not_in_layer(layer): +def test_component_not_in_layer(layer: Layer) -> None: with pytest.raises(KeyError, match="b"): Component("b", (1, 0, 0, 1, 0, 0)).getBounds(layer) with pytest.raises(KeyError, match="b"): diff --git a/tests/objects/test_contour.py b/tests/objects/test_contour.py index 4105931f..e52b19cf 100644 --- a/tests/objects/test_contour.py +++ b/tests/objects/test_contour.py @@ -3,10 +3,11 @@ import pytest from ufoLib2.objects import Glyph +from ufoLib2.objects.contour import Contour @pytest.fixture -def contour(): +def contour() -> Contour: g = Glyph("a") pen = g.getPen() pen.moveTo((0, 0)) @@ -15,12 +16,12 @@ def contour(): return g.contours[0] -def test_contour_getBounds(contour): +def test_contour_getBounds(contour: Contour) -> None: assert contour.getBounds() == (0, 0, 7.5, 20) assert contour.getBounds(layer={}) == (0, 0, 7.5, 20) assert contour.bounds == (0, 0, 7.5, 20) -def test_contour_getControlBounds(contour): +def test_contour_getControlBounds(contour: Contour) -> None: assert contour.getControlBounds() == (0, 0, 10, 20) assert contour.getControlBounds(layer={}) == (0, 0, 10, 20) diff --git a/tests/objects/test_font.py b/tests/objects/test_font.py index f3f91715..24c1b6d8 100644 --- a/tests/objects/test_font.py +++ b/tests/objects/test_font.py @@ -1,9 +1,11 @@ from __future__ import annotations +from pathlib import Path + from ufoLib2.objects import Font, Glyph, Guideline -def test_font_equality(datadir): +def test_font_equality(datadir: Path) -> None: font1 = Font.open(datadir / "UbuTestData.ufo") font2 = Font.open(datadir / "UbuTestData.ufo") @@ -16,7 +18,7 @@ class SubFont(Font): assert font1 != font3 -def test_font_mapping_behavior(ufo_UbuTestData): +def test_font_mapping_behavior(ufo_UbuTestData: Font) -> None: font = ufo_UbuTestData assert font["a"] is font.layers.defaultLayer["a"] @@ -33,7 +35,7 @@ def test_font_mapping_behavior(ufo_UbuTestData): assert "a" not in font.layers.defaultLayer -def test_font_defcon_behavior(ufo_UbuTestData): +def test_font_defcon_behavior(ufo_UbuTestData: Font) -> None: font = ufo_UbuTestData font.newGlyph("b") @@ -49,6 +51,7 @@ def test_font_defcon_behavior(ufo_UbuTestData): guideline = Guideline(x=1) font.appendGuideline(guideline) + assert font.info.guidelines is not None assert font.info.guidelines[-1] is guideline font.appendGuideline({"y": 1, "name": "asdf"}) @@ -62,7 +65,7 @@ def test_font_defcon_behavior(ufo_UbuTestData): assert "def" in font.layers -def test_nondefault_layer_name(ufo_UbuTestData, tmp_path): +def test_nondefault_layer_name(ufo_UbuTestData: Font, tmp_path: Path) -> None: font = ufo_UbuTestData font.layers.renameLayer("public.default", "abc") @@ -73,14 +76,22 @@ def test_nondefault_layer_name(ufo_UbuTestData, tmp_path): assert font2.layers.defaultLayer is font2.layers["abc"] -def test_bounds(ufo_UbuTestData): +def test_layer_order(ufo_UbuTestData: Font) -> None: + font = ufo_UbuTestData + + assert font.layers.layerOrder == ["public.default", "public.background"] + font.layers.layerOrder = ["public.background", "public.default"] + assert font.layers.layerOrder == ["public.background", "public.default"] + + +def test_bounds(ufo_UbuTestData: Font) -> None: font = ufo_UbuTestData assert font.bounds == (8, -11, 655, 693) assert font.controlPointBounds == (8, -11, 655, 693) -def test_data_images_init(): +def test_data_images_init() -> None: font = Font( data={"aaa": b"123", "bbb/c": b"456"}, images={"a.png": b"\x89PNG\r\n\x1a\n", "b.png": b"\x89PNG\r\n\x1a\n"}, diff --git a/tests/objects/test_glyph.py b/tests/objects/test_glyph.py index 61528b41..d481aa56 100644 --- a/tests/objects/test_glyph.py +++ b/tests/objects/test_glyph.py @@ -18,7 +18,7 @@ from ufoLib2.objects.misc import BoundingBox -def test_glyph_defcon_behavior(): +def test_glyph_defcon_behavior() -> None: glyph = Glyph() glyph.appendAnchor(Anchor(1, 2, "top")) glyph.appendAnchor({"x": 3, "y": 4, "name": "bottom"}) @@ -34,7 +34,7 @@ def test_glyph_defcon_behavior(): assert glyph.guidelines == [Guideline(x=1), Guideline(x=2)] -def test_copyDataFromGlyph(ufo_UbuTestData): +def test_copyDataFromGlyph(ufo_UbuTestData: Font) -> None: font = ufo_UbuTestData a = font["a"] @@ -67,7 +67,7 @@ def test_copyDataFromGlyph(ufo_UbuTestData): assert b.contours != a.contours assert b.components != a.components - def _assert_equal_but_distinct_objects(glyph1, glyph2): + def _assert_equal_but_distinct_objects(glyph1: Glyph, glyph2: Glyph) -> None: assert glyph1.width == glyph2.width assert glyph1.height == glyph2.height assert glyph1.unicodes == glyph2.unicodes @@ -105,7 +105,7 @@ def _assert_equal_but_distinct_objects(glyph1, glyph2): _assert_equal_but_distinct_objects(d, a) -def test_appendContour(ufo_UbuTestData): +def test_appendContour(ufo_UbuTestData: Font) -> None: font = ufo_UbuTestData A = font["A"] @@ -119,14 +119,14 @@ def test_appendContour(ufo_UbuTestData): assert A.contours[-1] is c with pytest.raises(TypeError, match="Expected Contour, found object"): - A.appendContour(object()) + A.appendContour(object()) # type: ignore -def test_glyph_without_name(): +def test_glyph_without_name() -> None: assert Glyph().name is None -def test_glyph_repr(): +def test_glyph_repr() -> None: g = Glyph() assert repr(g) == f"" @@ -134,7 +134,7 @@ def test_glyph_repr(): assert repr(g) == f"" -def test_glyph_get_bounds(): +def test_glyph_get_bounds() -> None: a = Glyph("a") pen = a.getPen() pen.moveTo((0, 0)) @@ -162,14 +162,14 @@ def test_glyph_get_bounds(): assert b.getControlBounds(layer) == (-50, 100, -40, 120) -def test_glyph_get_bounds_empty(): +def test_glyph_get_bounds_empty() -> None: g = Glyph() assert g.getBounds() is None assert g.getControlBounds() is None @pytest.fixture -def layer(): +def layer() -> Layer: a = Glyph("a") pen = a.getPen() pen.moveTo((8, 0)) @@ -186,7 +186,7 @@ def layer(): return layer -def test_glyph_get_margins(layer): +def test_glyph_get_margins(layer: Layer) -> None: a = layer["a"] # for simple contour glyphs without components, layer is optional/unused @@ -227,7 +227,7 @@ def test_glyph_get_margins(layer): assert c.getTopMargin() is None -def test_simple_glyph_set_left_margins(layer): +def test_simple_glyph_set_left_margins(layer: Layer) -> None: a = layer["a"] b = layer["b"] # same width, has component 'a' shifted +2 horizontally @@ -235,7 +235,7 @@ def test_simple_glyph_set_left_margins(layer): assert b.getLeftMargin(layer) == 10 assert a.width == 30 assert b.width == 30 - assert a.anchors[0].x, a.anchors[0].y == (10, 20) + assert (a.anchors[0].x, a.anchors[0].y) == (10, 30) a.setLeftMargin(8) # no change assert a.getLeftMargin() == 8 @@ -245,17 +245,17 @@ def test_simple_glyph_set_left_margins(layer): assert a.getLeftMargin() == 10 assert a.width == 32 # anchors were shifted - assert a.anchors[0].x, a.anchors[0].y == (12, 20) + assert (a.anchors[0].x, a.anchors[0].y) == (12, 30) # composite glyph "b" also shifts, but keeps original width assert b.getLeftMargin(layer) == 12 assert b.width == 30 a.setLeftMargin(-2) # -12 - assert a.getLeftMargin(-2) + assert a.getLeftMargin() == -2 assert a.width == 20 -def test_composite_glyph_set_left_margins(layer): +def test_composite_glyph_set_left_margins(layer: Layer) -> None: b = layer["b"] assert b.getLeftMargin(layer) == 10 @@ -266,7 +266,7 @@ def test_composite_glyph_set_left_margins(layer): assert b.width == 32 -def test_simple_glyph_set_right_margins(layer): +def test_simple_glyph_set_right_margins(layer: Layer) -> None: a = layer["a"] b = layer["b"] # same width, has component 'a' shifted +2 horizontally @@ -274,7 +274,7 @@ def test_simple_glyph_set_right_margins(layer): assert b.getRightMargin(layer) == 10 assert a.width == 30 assert b.width == 30 - assert a.anchors[0].x, a.anchors[0].y == (10, 20) + assert (a.anchors[0].x, a.anchors[0].y) == (10, 30) a.setRightMargin(12) # no change assert a.getRightMargin() == 12 @@ -284,7 +284,7 @@ def test_simple_glyph_set_right_margins(layer): assert a.getRightMargin() == 10 # only width changes, anchors stay same assert a.width == 28 - assert a.anchors[0].x, a.anchors[0].y == (10, 20) + assert (a.anchors[0].x, a.anchors[0].y) == (10, 30) # composite glyph "b" does _not_ change when "a" RSB changes assert b.getRightMargin(layer) == 10 assert b.width == 30 @@ -294,7 +294,7 @@ def test_simple_glyph_set_right_margins(layer): assert a.width == 16 -def test_composite_glyph_set_right_margins(layer): +def test_composite_glyph_set_right_margins(layer: Layer) -> None: b = layer["b"] assert b.getRightMargin(layer) == 10 @@ -305,7 +305,7 @@ def test_composite_glyph_set_right_margins(layer): assert b.width == 32 -def test_simple_glyph_set_bottom_margins(layer): +def test_simple_glyph_set_bottom_margins(layer: Layer) -> None: a = layer["a"] b = layer["b"] # same height/origin, has component 'a' shifted -5 vertically a.verticalOrigin = b.verticalOrigin = a.height = b.height = 30 @@ -322,7 +322,7 @@ def test_simple_glyph_set_bottom_margins(layer): assert b.height == b.verticalOrigin == 30 -def test_composite_glyph_set_bottom_margins(layer): +def test_composite_glyph_set_bottom_margins(layer: Layer) -> None: b = layer["b"] b.verticalOrigin = b.height = 30 @@ -334,7 +334,7 @@ def test_composite_glyph_set_bottom_margins(layer): assert b.height == 35 -def test_simple_glyph_set_top_margins(layer): +def test_simple_glyph_set_top_margins(layer: Layer) -> None: a = layer["a"] b = layer["b"] # same height/origin, has component 'a' shifted -5 vertically a.verticalOrigin = b.verticalOrigin = a.height = b.height = 30 @@ -351,7 +351,7 @@ def test_simple_glyph_set_top_margins(layer): assert b.height == b.verticalOrigin == 30 -def test_composite_glyph_set_top_margins(layer): +def test_composite_glyph_set_top_margins(layer: Layer) -> None: b = layer["b"] b.verticalOrigin = b.height = 30 diff --git a/tests/objects/test_layer.py b/tests/objects/test_layer.py index 061d2330..6f8589f5 100644 --- a/tests/objects/test_layer.py +++ b/tests/objects/test_layer.py @@ -5,7 +5,7 @@ from ufoLib2.objects import Glyph, Layer -def test_init_layer_with_glyphs_dict(): +def test_init_layer_with_glyphs_dict() -> None: a = Glyph() b = Glyph() @@ -28,10 +28,10 @@ def test_init_layer_with_glyphs_dict(): Layer(glyphs={"a": a, "b": a}) with pytest.raises(TypeError, match="Expected Glyph, found int"): - Layer(glyphs={"a": 1}) + Layer(glyphs={"a": 1}) # type: ignore -def test_init_layer_with_glyphs_list(): +def test_init_layer_with_glyphs_list() -> None: a = Glyph("a") b = Glyph("b") layer = Layer(glyphs=[a, b]) @@ -50,10 +50,10 @@ def test_init_layer_with_glyphs_list(): Layer(glyphs=[a, b, Glyph("b")]) with pytest.raises(TypeError, match="Expected Glyph, found int"): - Layer(glyphs=[1]) + Layer(glyphs=[1]) # type: ignore -def test_addGlyph(): +def test_addGlyph() -> None: a = Glyph("a") layer = Layer() @@ -67,7 +67,7 @@ def test_addGlyph(): layer.addGlyph(a) -def test_insertGlyph(): +def test_insertGlyph() -> None: g = Glyph() pen = g.getPen() pen.moveTo((0, 0)) @@ -99,7 +99,7 @@ def test_insertGlyph(): layer.insertGlyph(g) -def test_newGlyph(): +def test_newGlyph() -> None: layer = Layer() a = layer.newGlyph("a") @@ -110,7 +110,7 @@ def test_newGlyph(): layer.newGlyph("a") -def test_renameGlyph(): +def test_renameGlyph() -> None: g = Glyph() layer = Layer(glyphs={"a": g}) diff --git a/tests/objects/test_object_lib.py b/tests/objects/test_object_lib.py index d3998a7b..8974dfbb 100644 --- a/tests/objects/test_object_lib.py +++ b/tests/objects/test_object_lib.py @@ -1,9 +1,11 @@ from __future__ import annotations +from pathlib import Path + from ufoLib2.objects import Anchor, Font, Guideline -def test_object_lib_roundtrip(tmp_path): +def test_object_lib_roundtrip(tmp_path: Path) -> None: ufo = Font() ufo.info.guidelines = [Guideline(x=100), Guideline(y=200)] @@ -45,6 +47,7 @@ def test_object_lib_roundtrip(tmp_path): # Roundtrip ufo_reload = Font.open(tmp_path / "test.ufo") + assert ufo_reload.info.guidelines is not None reload_guideline_lib = ufo_reload.objectLib(ufo_reload.info.guidelines[1]) reload_glyph = ufo_reload["test"] reload_glyph_guideline_lib = reload_glyph.objectLib(reload_glyph.guidelines[1]) @@ -61,7 +64,7 @@ def test_object_lib_roundtrip(tmp_path): assert reload_component_lib == component_lib -def test_object_lib_prune(tmp_path): +def test_object_lib_prune(tmp_path: Path) -> None: ufo = Font() ufo.info.guidelines = [Guideline(x=100), Guideline(y=200)] diff --git a/tests/test_ufoLib2.py b/tests/test_ufoLib2.py index d18d81ce..3c233be3 100644 --- a/tests/test_ufoLib2.py +++ b/tests/test_ufoLib2.py @@ -1,23 +1,23 @@ from __future__ import annotations -from collections import OrderedDict from copy import deepcopy +from pathlib import Path import pytest from fontTools import ufoLib import ufoLib2 import ufoLib2.objects -from ufoLib2.objects import Layer, LayerSet +from ufoLib2.objects import Font, Layer, LayerSet from ufoLib2.objects.misc import _NOT_LOADED -def test_import_version(): +def test_import_version() -> None: assert hasattr(ufoLib2, "__version__") - assert isinstance(ufoLib2.__version__, str) + assert isinstance(ufoLib2.__version__, str) # type: ignore -def test_LayerSet_load_layers_on_iteration(tmp_path): +def test_LayerSet_load_layers_on_iteration(tmp_path: Path) -> None: ufo = ufoLib2.Font() ufo.layers.newLayer("test") ufo_save_path = tmp_path / "test.ufo" @@ -25,23 +25,23 @@ def test_LayerSet_load_layers_on_iteration(tmp_path): ufo = ufoLib2.Font.open(ufo_save_path) assert set(ufo.layers.keys()) == {"public.default", "test"} for layer in ufo.layers: - assert layer is not _NOT_LOADED + assert layer is not _NOT_LOADED # type: ignore -def test_lazy_data_loading_saveas(ufo_UbuTestData, tmp_path): +def test_lazy_data_loading_saveas(ufo_UbuTestData: Font, tmp_path: Path) -> None: ufo = ufo_UbuTestData ufo_path = tmp_path / "UbuTestData2.ufo" ufo.save(ufo_path) assert all(v is not _NOT_LOADED for v in ufo.data._data.values()) -def test_lazy_data_loading_inplace_no_load(ufo_UbuTestData): +def test_lazy_data_loading_inplace_no_load(ufo_UbuTestData: Font) -> None: ufo = ufo_UbuTestData ufo.save() assert all(v is _NOT_LOADED for v in ufo.data._data.values()) -def test_lazy_data_loading_inplace_load_some(ufo_UbuTestData): +def test_lazy_data_loading_inplace_load_some(ufo_UbuTestData: Font) -> None: ufo = ufo_UbuTestData some_data = b"abc" ufo.data["com.github.fonttools.ttx/T_S_I__0.ttx"] = some_data @@ -52,7 +52,7 @@ def test_lazy_data_loading_inplace_load_some(ufo_UbuTestData): assert ufo.data["com.github.fonttools.ttx/T_S_I__0.ttx"] == some_data -def test_deepcopy_lazy_object(datadir): +def test_deepcopy_lazy_object(datadir: Path) -> None: path = datadir / "UbuTestData.ufo" font1 = ufoLib2.Font.open(path, lazy=True) @@ -84,7 +84,7 @@ def test_deepcopy_lazy_object(datadir): assert font2.path is None -def test_unlazify(datadir): +def test_unlazify(datadir: Path) -> None: reader = ufoLib.UFOReader(datadir / "UbuTestData.ufo") font = ufoLib2.Font.read(reader, lazy=True) @@ -96,28 +96,28 @@ def test_unlazify(datadir): assert font._lazy is False -def test_auto_unlazify_font(datadir): +def test_auto_unlazify_font(datadir: Path) -> None: font1 = ufoLib2.Font.open(datadir / "UbuTestData.ufo", lazy=True) font2 = ufoLib2.Font.open(datadir / "UbuTestData.ufo", lazy=False) assert font1 == font2 -def test_auto_unlazify_data(datadir): +def test_auto_unlazify_data(datadir: Path) -> None: font1 = ufoLib2.Font.open(datadir / "UbuTestData.ufo", lazy=True) font2 = ufoLib2.Font.open(datadir / "UbuTestData.ufo", lazy=False) assert font1.data == font2.data -def test_auto_unlazify_images(datadir): +def test_auto_unlazify_images(datadir: Path) -> None: font1 = ufoLib2.Font.open(datadir / "UbuTestData.ufo", lazy=True) font2 = ufoLib2.Font.open(datadir / "UbuTestData.ufo", lazy=False) assert font1.images == font2.images -def test_font_eq_and_ne(ufo_UbuTestData): +def test_font_eq_and_ne(ufo_UbuTestData: Font) -> None: font1 = ufo_UbuTestData font2 = deepcopy(font1) @@ -128,19 +128,19 @@ def test_font_eq_and_ne(ufo_UbuTestData): assert font1 != font2 -def test_empty_layerset(): +def test_empty_layerset() -> None: with pytest.raises(ValueError): - LayerSet(layers={}, defaultLayer=None) + LayerSet(layers={}, defaultLayer=None) # type: ignore -def test_default_layerset(): +def test_default_layerset() -> None: layers = LayerSet.default() assert len(layers) == 1 assert "public.default" in layers assert len(layers["public.default"]) == 0 -def test_custom_layerset(): +def test_custom_layerset() -> None: default = Layer() ls1 = LayerSet.from_iterable([default]) assert next(iter(ls1)) is ls1.defaultLayer @@ -151,12 +151,12 @@ def test_custom_layerset(): ls2 = LayerSet.from_iterable([Layer(name="abc")], defaultLayerName="abc") assert ls2["abc"] is ls2.defaultLayer - layers2 = OrderedDict() + layers2 = {} layers2["public.default"] = default - LayerSet(layers=layers2, defaultLayer=default) + LayerSet(layers=layers2, defaultLayer=default) # type: ignore -def test_guidelines(): +def test_guidelines() -> None: font = ufoLib2.Font() # accept either a mapping or a Guideline object @@ -170,7 +170,7 @@ def test_guidelines(): ] # setter should clear existing guidelines - font.guidelines = [{"x": 100}, ufoLib2.objects.Guideline(y=20)] + font.guidelines = [{"x": 100}, ufoLib2.objects.Guideline(y=20)] # type: ignore assert len(font.guidelines) == 2 assert font.guidelines == [ diff --git a/tox.ini b/tox.ini index 9124f837..e55a83b3 100644 --- a/tox.ini +++ b/tox.ini @@ -28,7 +28,7 @@ deps = commands = black --check --diff . isort --skip-gitignore --check-only --diff src tests - mypy src tests + mypy --strict src tests flake8 [testenv:docs] From e10ef1724c5002e77149ccf61fbf6e828d969fbe Mon Sep 17 00:00:00 2001 From: Nikolaus Waxweiler Date: Tue, 9 Nov 2021 16:02:20 +0000 Subject: [PATCH 3/3] Make ABC typings work in Python 3.7 --- src/ufoLib2/objects/contour.py | 10 ++++++++-- src/ufoLib2/objects/image.py | 10 ++++++++-- src/ufoLib2/objects/misc.py | 19 ++++++++++++++++--- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/ufoLib2/objects/contour.py b/src/ufoLib2/objects/contour.py index 88121293..2338b7ae 100644 --- a/src/ufoLib2/objects/contour.py +++ b/src/ufoLib2/objects/contour.py @@ -2,7 +2,7 @@ import warnings from collections.abc import MutableSequence -from typing import Iterable, Iterator, overload +from typing import TYPE_CHECKING, Iterable, Iterator, overload from attr import define, field from fontTools.pens.basePen import AbstractPen @@ -12,9 +12,15 @@ from ufoLib2.objects.point import Point from ufoLib2.typing import GlyphSet +# For Python 3.7 compatibility. +if TYPE_CHECKING: + ContourMapping = MutableSequence[Point] +else: + ContourMapping = MutableSequence + @define -class Contour(MutableSequence[Point]): +class Contour(ContourMapping): """Represents a contour as a list of points. Behavior: diff --git a/src/ufoLib2/objects/image.py b/src/ufoLib2/objects/image.py index fbf376d2..bf8bbe46 100644 --- a/src/ufoLib2/objects/image.py +++ b/src/ufoLib2/objects/image.py @@ -1,16 +1,22 @@ from __future__ import annotations from collections.abc import Mapping -from typing import Any, Iterator +from typing import TYPE_CHECKING, Any, Iterator from attr import define, field from fontTools.misc.transform import Identity, Transform from .misc import _convert_transform +# For Python 3.7 compatibility. +if TYPE_CHECKING: + ImageMapping = Mapping[str, Any] +else: + ImageMapping = Mapping + @define -class Image(Mapping[str, Any]): +class Image(ImageMapping): """Represents a background image reference. See http://unifiedfontobject.org/versions/ufo3/images/ and diff --git a/src/ufoLib2/objects/misc.py b/src/ufoLib2/objects/misc.py index 68195fd0..5a66f6e3 100644 --- a/src/ufoLib2/objects/misc.py +++ b/src/ufoLib2/objects/misc.py @@ -5,7 +5,7 @@ from abc import abstractmethod from collections.abc import Mapping, MutableMapping from copy import deepcopy -from typing import Any, Iterator, NamedTuple, Sequence, TypeVar, cast +from typing import TYPE_CHECKING, Any, Iterator, NamedTuple, Sequence, TypeVar, cast import attr from attr import define, field @@ -115,9 +115,15 @@ class Placeholder: # Create a generic variable for mypy that can be 'DataStore' or any subclass. Tds = TypeVar("Tds", bound="DataStore") +# For Python 3.7 compatibility. +if TYPE_CHECKING: + DataStoreMapping = MutableMapping[str, bytes] +else: + DataStoreMapping = MutableMapping + @define -class DataStore(MutableMapping[str, bytes]): +class DataStore(DataStoreMapping): """Represents the base class for ImageSet and DataSet. Both behave like a dictionary that loads its "values" lazily by default and only @@ -267,7 +273,14 @@ def fileNames(self) -> list[str]: return list(self._data.keys()) -class AttrDictMixin(Mapping[str, Any]): +# For Python 3.7 compatibility. +if TYPE_CHECKING: + AttrDictMixinMapping = Mapping[str, Any] +else: + AttrDictMixinMapping = Mapping + + +class AttrDictMixin(AttrDictMixinMapping): """Read attribute values using mapping interface. For use with Anchors and Guidelines classes, where client code