Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

use on_setattr to auto-convert dict to Lib and Kerning #188

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/ufoLib2/objects/font.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.

Expand Down
8 changes: 3 additions & 5 deletions src/ufoLib2/objects/glyph.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 4 additions & 6 deletions src/ufoLib2/objects/layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 0 additions & 9 deletions src/ufoLib2/objects/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 20 additions & 1 deletion tests/test_ufoLib2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)