From 0e0702e9e57ae19e8ced80d9300900c747571651 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 16 Dec 2021 17:08:33 +0000 Subject: [PATCH] use on_setattr to auto-convert dict to Lib and Kerning --- src/ufoLib2/objects/font.py | 12 ++++++------ src/ufoLib2/objects/glyph.py | 8 +++----- src/ufoLib2/objects/layer.py | 10 ++++------ src/ufoLib2/objects/lib.py | 9 --------- tests/test_ufoLib2.py | 21 ++++++++++++++++++++- 5 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/ufoLib2/objects/font.py b/src/ufoLib2/objects/font.py index d022d58b..8a3288a3 100644 --- a/src/ufoLib2/objects/font.py +++ b/src/ufoLib2/objects/font.py @@ -18,7 +18,7 @@ import fs.base import fs.tempfs -from attr import define, field +from attr import define, field, setters from fontTools.ufoLib import UFOFileStructure, UFOReader, UFOWriter from ufoLib2.constants import DEFAULT_LAYER_NAME @@ -31,7 +31,7 @@ from ufoLib2.objects.kerning import Kerning, KerningPair from ufoLib2.objects.layer import Layer from ufoLib2.objects.layerSet import LayerSet -from ufoLib2.objects.lib import Lib, _convert_Lib, _get_lib, _set_lib +from ufoLib2.objects.lib import Lib, _convert_Lib from ufoLib2.objects.misc import ( BoundingBox, _deepcopy_unlazify_attrs, @@ -141,11 +141,13 @@ class Font: groups: Dict[str, List[str]] = field(factory=dict) """Dict[str, List[str]]: A mapping of group names to a list of glyph names.""" - kerning: Kerning = field(factory=Kerning, converter=_convert_Kerning) + kerning: Kerning = field( + factory=Kerning, converter=_convert_Kerning, on_setattr=setters.convert + ) """Dict[Tuple[str, str], float]: A mapping of a tuple of first and second kerning pair to a kerning value.""" - _lib: Lib = field(factory=Lib, converter=_convert_Lib) + lib: Lib = field(factory=Lib, converter=_convert_Lib, on_setattr=setters.convert) """Dict[str, PlistEncodable]: A mapping of keys to arbitrary plist values.""" data: DataSet = field(factory=DataSet, converter=_convert_DataSet) @@ -359,8 +361,6 @@ def guidelines(self, value: Iterable[Guideline | Mapping[str, Any]]) -> None: for guideline in value: self.appendGuideline(guideline) - lib = property(_get_lib, _set_lib) - def objectLib(self, object: HasIdentifier) -> dict[str, Any]: """Return the lib for an object with an identifier, as stored in a font's lib. diff --git a/src/ufoLib2/objects/glyph.py b/src/ufoLib2/objects/glyph.py index b90eb5be..d13ea557 100644 --- a/src/ufoLib2/objects/glyph.py +++ b/src/ufoLib2/objects/glyph.py @@ -3,7 +3,7 @@ from copy import deepcopy from typing import Any, Iterator, List, Mapping, Optional, cast -from attr import define, field +from attr import define, field, setters from fontTools.misc.transform import Transform from fontTools.pens.basePen import AbstractPen from fontTools.pens.pointPen import ( @@ -17,7 +17,7 @@ from ufoLib2.objects.contour import Contour from ufoLib2.objects.guideline import Guideline from ufoLib2.objects.image import Image -from ufoLib2.objects.lib import Lib, _convert_Lib, _get_lib, _set_lib +from ufoLib2.objects.lib import Lib, _convert_Lib from ufoLib2.objects.misc import BoundingBox, _object_lib, getBounds, getControlBounds from ufoLib2.pointPens.glyphPointPen import GlyphPointPen from ufoLib2.typing import GlyphSet, HasIdentifier @@ -67,7 +67,7 @@ class Glyph: _image: Image = field(factory=Image) - _lib: Lib = field(factory=Lib, converter=_convert_Lib) + lib: Lib = field(factory=Lib, converter=_convert_Lib, on_setattr=setters.convert) """The glyph's mapping of string keys to arbitrary data.""" note: Optional[str] = None @@ -102,8 +102,6 @@ def __repr__(self) -> str: hex(id(self)), ) - lib = property(_get_lib, _set_lib) - @property def anchors(self) -> list[Anchor]: """The list of anchors the glyph contains. diff --git a/src/ufoLib2/objects/layer.py b/src/ufoLib2/objects/layer.py index 670dcdf7..3b2ff26a 100644 --- a/src/ufoLib2/objects/layer.py +++ b/src/ufoLib2/objects/layer.py @@ -12,12 +12,12 @@ overload, ) -from attr import define, field +from attr import define, field, setters from fontTools.ufoLib.glifLib import GlyphSet from ufoLib2.constants import DEFAULT_LAYER_NAME from ufoLib2.objects.glyph import Glyph -from ufoLib2.objects.lib import Lib, _convert_Lib, _get_lib, _set_lib +from ufoLib2.objects.lib import Lib, _convert_Lib from ufoLib2.objects.misc import ( BoundingBox, _deepcopy_unlazify_attrs, @@ -107,7 +107,7 @@ class Layer: color: Optional[str] = None """The color assigned to the layer.""" - _lib: Lib = field(factory=Lib, converter=_convert_Lib) + lib: Lib = field(factory=Lib, converter=_convert_Lib, on_setattr=setters.convert) """The layer's lib for mapping string keys to arbitrary data.""" _default: bool = False @@ -237,8 +237,6 @@ def name(self) -> str: """The name of the layer.""" return self._name - lib = property(_get_lib, _set_lib) - @property def default(self) -> bool: """Read-only property. To change the font's default layer use the @@ -387,7 +385,7 @@ def _unstructure(self, converter: GenConverter) -> dict[str, Any]: for key, value, default in [ ("default", self._default, self._name == DEFAULT_LAYER_NAME), ("glyphs", glyphs, {}), - ("lib", self._lib, {}), + ("lib", self.lib, {}), ]: if not converter.omit_if_default or value != default: d[key] = value diff --git a/src/ufoLib2/objects/lib.py b/src/ufoLib2/objects/lib.py index 4705851a..33e65fb9 100644 --- a/src/ufoLib2/objects/lib.py +++ b/src/ufoLib2/objects/lib.py @@ -26,15 +26,6 @@ def _convert_Lib(value: Mapping[str, Any]) -> Lib: return value if isinstance(value, Lib) else Lib(value) -# getter/setter properties used by Font, Layer, Glyph -def _get_lib(self: Any) -> Lib: - return cast(Lib, self._lib) - - -def _set_lib(self: Any, value: Mapping[str, Any]) -> None: - self._lib = _convert_Lib(value) - - def is_data_dict(value: Any) -> bool: return ( isinstance(value, Mapping) diff --git a/tests/test_ufoLib2.py b/tests/test_ufoLib2.py index 59cd620b..79d5912c 100644 --- a/tests/test_ufoLib2.py +++ b/tests/test_ufoLib2.py @@ -2,13 +2,14 @@ from copy import deepcopy from pathlib import Path +from typing import Any, Type import pytest from fontTools import ufoLib import ufoLib2 import ufoLib2.objects -from ufoLib2.objects import Features, Font, Layer, LayerSet +from ufoLib2.objects import Features, Font, Glyph, Kerning, Layer, LayerSet, Lib from ufoLib2.objects.layerSet import _LAYER_NOT_LOADED from ufoLib2.objects.misc import _DATA_NOT_LOADED @@ -265,3 +266,21 @@ def test_woff_metadata(datadir: Path, tmp_path: Path) -> None: def test_features_normalize_newlines() -> None: assert Features("a\r\nb\rc\n").normalize_newlines().text == "a\nb\nc\n" + + +@pytest.mark.parametrize( + "klass, attr_name, attr_type, obj", + [ + (Font, "lib", Lib, {"foo": 1}), + (Font, "kerning", Kerning, {("a", "b"): -10}), + (Glyph, "lib", Lib, {"bar": [2, 3]}), + ], +) +def test_font_on_setattr( + klass: Type[Any], attr_name: str, attr_type: Type[Any], obj: Any +) -> None: + o = klass() + assert isinstance(getattr(o, attr_name), attr_type) + assert not isinstance(obj, attr_type) + setattr(o, attr_name, obj) + assert isinstance(getattr(o, attr_name), attr_type)