daltonmaag / statmake

Generate STAT tables for variable fonts from .stylespace files
MIT License
39 stars 11 forks source link

Test failures with latest cattrs #42

Closed fabaff closed 2 years ago

fabaff commented 2 years ago

With cattrs-22.1.0 two tests are failing. With cattrs-1.10.0 they are passing.

============================= test session starts ==============================
platform linux -- Python 3.10.4, pytest-7.1.1, pluggy-1.0.0
rootdir: /build/source, configfile: pytest.ini
collected 28 items                                                             

tests/test_cli.py ....                                                   [ 14%]
tests/test_make_stat.py F......F...............                          [ 96%]
tests/test_serialize.py .                                                [100%]

=================================== FAILURES ===================================
______________________ test_load_stylespace_broken_range _______________________

datadir = PosixPath('/build/source/tests/data')

    def test_load_stylespace_broken_range(datadir):
        with pytest.raises(StylespaceError, match=r"Range .*"):
>           statmake.classes.Stylespace.from_file(datadir / "TestBroken.stylespace")

tests/test_make_stat.py:14: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/nix/store/8a8jphw3hm0k6hs8a2qxvry4f61rybwn-python3.10-statmake-0.4.1/lib/python3.10/site-packages/statmake/classes.py:280: in from_file
    return cls.from_bytes(fp.read())
/nix/store/8a8jphw3hm0k6hs8a2qxvry4f61rybwn-python3.10-statmake-0.4.1/lib/python3.10/site-packages/statmake/classes.py:274: in from_bytes
    return cls.from_dict(stylespace_content_parsed)
/nix/store/8a8jphw3hm0k6hs8a2qxvry4f61rybwn-python3.10-statmake-0.4.1/lib/python3.10/site-packages/statmake/classes.py:259: in from_dict
    return converter.structure(dict_data, cls)
/nix/store/cq2vc2c1lf4846fbk8yqccl1ciq49i1z-python3.10-cattrs-22.1.0/lib/python3.10/site-packages/cattrs/converters.py:281: in structure
    return self._structure_func.dispatch(cl)(obj, cl)
/nix/store/cq2vc2c1lf4846fbk8yqccl1ciq49i1z-python3.10-cattrs-22.1.0/lib/python3.10/site-packages/cattrs/converters.py:446: in structure_attrs_fromdict
    conv_obj[name] = self._structure_attribute(a, val)
/nix/store/cq2vc2c1lf4846fbk8yqccl1ciq49i1z-python3.10-cattrs-22.1.0/lib/python3.10/site-packages/cattrs/converters.py:422: in _structure_attribute
    return self._structure_func.dispatch(type_)(value, type_)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <cattrs.converters.Converter object at 0x7ffff5793060>
obj = [{'locations': [{'name': 'Extra Light', 'range': [200], 'value': 200}, {'name': 'Light', 'range': [250, 350], 'value':...g': 0, 'tag': 'wght'}, {'locations': [{'name': 'Italic', 'value': 1}], 'name': 'Italic', 'ordering': 1, 'tag': 'ital'}]
cl = typing.List[statmake.classes.Axis]

    def _structure_list(self, obj, cl):
        """Convert an iterable to a potentially generic list."""
        if is_bare(cl) or cl.__args__[0] is Any:
            res = [e for e in obj]
        else:
            elem_type = cl.__args__[0]
            handler = self._structure_func.dispatch(elem_type)
            if self.detailed_validation:
                errors = []
                res = []
                ix = 0  # Avoid `enumerate` for performance.
                for e in obj:
                    try:
                        res.append(handler(e, elem_type))
                    except Exception as e:
                        e.__note__ = f"Structuring {cl} @ index {ix}"
                        errors.append(e)
                    finally:
                        ix += 1
                if errors:
>                   raise IterableValidationError(
                        f"While structuring {cl!r}", errors, cl
                    )
E                   cattrs.errors.IterableValidationError: While structuring typing.List[statmake.classes.Axis]

/nix/store/cq2vc2c1lf4846fbk8yqccl1ciq49i1z-python3.10-cattrs-22.1.0/lib/python3.10/site-packages/cattrs/converters.py:470: IterableValidationError
________________ test_load_stylespace_broken_multilingual_no_en ________________

datadir = PosixPath('/build/source/tests/data')

    def test_load_stylespace_broken_multilingual_no_en(datadir):
        with pytest.raises(StylespaceError, match=r".* must have a default English .*"):
>           statmake.classes.Stylespace.from_file(
                datadir / "TestMultilingualNoEn.stylespace"
            )

tests/test_make_stat.py:65: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/nix/store/8a8jphw3hm0k6hs8a2qxvry4f61rybwn-python3.10-statmake-0.4.1/lib/python3.10/site-packages/statmake/classes.py:280: in from_file
    return cls.from_bytes(fp.read())
/nix/store/8a8jphw3hm0k6hs8a2qxvry4f61rybwn-python3.10-statmake-0.4.1/lib/python3.10/site-packages/statmake/classes.py:274: in from_bytes
    return cls.from_dict(stylespace_content_parsed)
/nix/store/8a8jphw3hm0k6hs8a2qxvry4f61rybwn-python3.10-statmake-0.4.1/lib/python3.10/site-packages/statmake/classes.py:259: in from_dict
    return converter.structure(dict_data, cls)
/nix/store/cq2vc2c1lf4846fbk8yqccl1ciq49i1z-python3.10-cattrs-22.1.0/lib/python3.10/site-packages/cattrs/converters.py:281: in structure
    return self._structure_func.dispatch(cl)(obj, cl)
/nix/store/cq2vc2c1lf4846fbk8yqccl1ciq49i1z-python3.10-cattrs-22.1.0/lib/python3.10/site-packages/cattrs/converters.py:446: in structure_attrs_fromdict
    conv_obj[name] = self._structure_attribute(a, val)
/nix/store/cq2vc2c1lf4846fbk8yqccl1ciq49i1z-python3.10-cattrs-22.1.0/lib/python3.10/site-packages/cattrs/converters.py:422: in _structure_attribute
    return self._structure_func.dispatch(type_)(value, type_)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <cattrs.converters.Converter object at 0x7ffff5710a50>
obj = [{'locations': [{'name': 'Light', 'value': 300}, {'flags': ['ElidableAxisValueName'], 'linked_value': 700, 'name': {'d...e': {'de': 'Italienisch', 'en': 'Italic'}, 'value': 1}], 'name': {'de': 'Italienisch', 'en': 'Italic'}, 'tag': 'ital'}]
cl = typing.List[statmake.classes.Axis]

    def _structure_list(self, obj, cl):
        """Convert an iterable to a potentially generic list."""
        if is_bare(cl) or cl.__args__[0] is Any:
            res = [e for e in obj]
        else:
            elem_type = cl.__args__[0]
            handler = self._structure_func.dispatch(elem_type)
            if self.detailed_validation:
                errors = []
                res = []
                ix = 0  # Avoid `enumerate` for performance.
                for e in obj:
                    try:
                        res.append(handler(e, elem_type))
                    except Exception as e:
                        e.__note__ = f"Structuring {cl} @ index {ix}"
                        errors.append(e)
                    finally:
                        ix += 1
                if errors:
>                   raise IterableValidationError(
                        f"While structuring {cl!r}", errors, cl
                    )
E                   cattrs.errors.IterableValidationError: While structuring typing.List[statmake.classes.Axis]

/nix/store/cq2vc2c1lf4846fbk8yqccl1ciq49i1z-python3.10-cattrs-22.1.0/lib/python3.10/site-packages/cattrs/converters.py:470: IterableValidationError
=========================== short test summary info ============================
FAILED tests/test_make_stat.py::test_load_stylespace_broken_range - cattrs.er...
FAILED tests/test_make_stat.py::test_load_stylespace_broken_multilingual_no_en
========================= 2 failed, 26 passed in 1.58s =========================

Thanks

This will most likely only affect distribution packages as cattrs = "^1.1" will prevent the usage of later cattrs releases.

madig commented 2 years ago

Thanks for bringing this to my attention. It seems the library should work, but my intentionally broken test files raise a different error now which is not handled by the tests. I tried to do a quick fix but now I keep finding more issues. Let's see.

Artturin commented 1 year ago
python3.10-statmake> ============================= test session starts ==============================
python3.10-statmake> platform linux -- Python 3.10.8, pytest-7.1.3, pluggy-1.0.0
python3.10-statmake> rootdir: /build/source
python3.10-statmake> collected 29 items
python3.10-statmake> tests/test_cli.py ....                                                   [ 13%]
python3.10-statmake> tests/test_make_stat.py F.......................                         [ 96%]
python3.10-statmake> tests/test_serialize.py .                                                [100%]
python3.10-statmake> =================================== FAILURES ===================================
python3.10-statmake> ______________________ test_load_stylespace_broken_range _______________________
python3.10-statmake> datadir = PosixPath('/build/source/tests/data')
python3.10-statmake>     def test_load_stylespace_broken_range(datadir):
python3.10-statmake>         with pytest.raises(StylespaceError, match=r"Range .*"):
python3.10-statmake> >           statmake.classes.Stylespace.from_file(datadir / "TestBroken.stylespace")
python3.10-statmake> tests/test_make_stat.py:14:
python3.10-statmake> _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
python3.10-statmake> /nix/store/gp62i8ckbz41is00kpvjbyv5lmcsv95c-python3.10-statmake-0.5.1/lib/python3.10/site-packages/statmake/classes.py:288: in from_file
python3.10-statmake>     return cls.from_bytes(fp.read(), detailed_validation)
python3.10-statmake> /nix/store/gp62i8ckbz41is00kpvjbyv5lmcsv95c-python3.10-statmake-0.5.1/lib/python3.10/site-packages/statmake/classes.py:278: in from_bytes
python3.10-statmake>     return cls.from_dict(stylespace_content_parsed, detailed_validation)
python3.10-statmake> /nix/store/gp62i8ckbz41is00kpvjbyv5lmcsv95c-python3.10-statmake-0.5.1/lib/python3.10/site-packages/statmake/classes.py:261: in from_dict
python3.10-statmake>     return converter.structure(dict_data, cls)
python3.10-statmake> /nix/store/5mbdy53diadpqp390k3mpfn366b78ynm-python3.10-cattrs-22.2.0/lib/python3.10/site-packages/cattrs/converters.py:309: in structure
python3.10-statmake>     return self._structure_func.dispatch(cl)(obj, cl)
python3.10-statmake> <cattrs generated structure statmake.classes.Stylespace-4>:8: in structure_Stylespace
python3.10-statmake>     __c_structure_axes(o['axes'], __c_type_axes),
python3.10-statmake> /nix/store/5mbdy53diadpqp390k3mpfn366b78ynm-python3.10-cattrs-22.2.0/lib/python3.10/site-packages/cattrs/converters.py:514: in _structure_list
python3.10-statmake>     res = [handler(e, elem_type) for e in obj]
python3.10-statmake> /nix/store/5mbdy53diadpqp390k3mpfn366b78ynm-python3.10-cattrs-22.2.0/lib/python3.10/site-packages/cattrs/converters.py:514: in <listcomp>
python3.10-statmake>     res = [handler(e, elem_type) for e in obj]
python3.10-statmake> <cattrs generated structure statmake.classes.Axis-4>:4: in structure_Axis
python3.10-statmake>     res['locations'] = __c_structure_locations(o['locations'], __c_type_locations)
python3.10-statmake> /nix/store/5mbdy53diadpqp390k3mpfn366b78ynm-python3.10-cattrs-22.2.0/lib/python3.10/site-packages/cattrs/converters.py:514: in _structure_list
python3.10-statmake>     res = [handler(e, elem_type) for e in obj]
python3.10-statmake> /nix/store/5mbdy53diadpqp390k3mpfn366b78ynm-python3.10-cattrs-22.2.0/lib/python3.10/site-packages/cattrs/converters.py:514: in <listcomp>
python3.10-statmake>     res = [handler(e, elem_type) for e in obj]
python3.10-statmake> /nix/store/5mbdy53diadpqp390k3mpfn366b78ynm-python3.10-cattrs-22.2.0/lib/python3.10/site-packages/cattrs/converters.py:403: in structure_attrs_union
python3.10-statmake>     return self.structure(obj, dis_fn(obj))
python3.10-statmake> /nix/store/5mbdy53diadpqp390k3mpfn366b78ynm-python3.10-cattrs-22.2.0/lib/python3.10/site-packages/cattrs/converters.py:309: in structure
python3.10-statmake>     return self._structure_func.dispatch(cl)(obj, cl)
python3.10-statmake> <cattrs generated structure statmake.classes.LocationFormat2-4>:8: in structure_LocationFormat2
python3.10-statmake>     __c_structure_range(o['range'], __c_type_range),
python3.10-statmake> _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
python3.10-statmake> self = <cattrs.converters.Converter object at 0x7ffff57f4cc0>, obj = [200]
python3.10-statmake> tup = typing.Tuple[float, float]
python3.10-statmake>     def _structure_tuple(self, obj: Any, tup: Type[T]) -> T:
python3.10-statmake>         """Deal with structuring into a tuple."""
python3.10-statmake>         if tup in (Tuple, tuple):
python3.10-statmake>             tup_params = None
python3.10-statmake>         else:
python3.10-statmake>             tup_params = tup.__args__
python3.10-statmake>         has_ellipsis = tup_params and tup_params[-1] is Ellipsis
python3.10-statmake>         if tup_params is None or (has_ellipsis and tup_params[0] is Any):
python3.10-statmake>             # Just a Tuple. (No generic information.)
python3.10-statmake>             return tuple(obj)
python3.10-statmake>         if has_ellipsis:
python3.10-statmake>             # We're dealing with a homogenous tuple, Tuple[int, ...]
python3.10-statmake>             tup_type = tup_params[0]
python3.10-statmake>             conv = self._structure_func.dispatch(tup_type)
python3.10-statmake>             if self.detailed_validation:
python3.10-statmake>                 errors = []
python3.10-statmake>                 res = []
python3.10-statmake>                 for ix, e in enumerate(obj):
python3.10-statmake>                     try:
python3.10-statmake>                         res.append(conv(e, tup_type))
python3.10-statmake>                     except Exception as exc:
python3.10-statmake>                         msg = f"Structuring {tup} @ index {ix}"
python3.10-statmake>                         exc.__notes__ = getattr(e, "__notes__", []) + [msg]
python3.10-statmake>                         errors.append(exc)
python3.10-statmake>                 if errors:
python3.10-statmake>                     raise IterableValidationError(
python3.10-statmake>                         f"While structuring {tup!r}", errors, tup
python3.10-statmake>                     )
python3.10-statmake>                 return tuple(res)
python3.10-statmake>             else:
python3.10-statmake>                 return tuple(conv(e, tup_type) for e in obj)
python3.10-statmake>         else:
python3.10-statmake>             # We're dealing with a heterogenous tuple.
python3.10-statmake>             exp_len = len(tup_params)
python3.10-statmake>             try:
python3.10-statmake>                 len_obj = len(obj)
python3.10-statmake>             except TypeError:
python3.10-statmake>                 pass  # most likely an unsized iterator, eg generator
python3.10-statmake>             else:
python3.10-statmake>                 if len_obj > exp_len:
python3.10-statmake>                     exp_len = len_obj
python3.10-statmake>             if self.detailed_validation:
python3.10-statmake>                 errors = []
python3.10-statmake>                 res = []
python3.10-statmake>                 for ix, (t, e) in enumerate(zip(tup_params, obj)):
python3.10-statmake>                     try:
python3.10-statmake>                         conv = self._structure_func.dispatch(t)
python3.10-statmake>                         res.append(conv(e, t))
python3.10-statmake>                     except Exception as exc:
python3.10-statmake>                         msg = f"Structuring {tup} @ index {ix}"
python3.10-statmake>                         exc.__notes__ = getattr(e, "__notes__", []) + [msg]
python3.10-statmake>                         errors.append(exc)
python3.10-statmake>                 if len(res) < exp_len:
python3.10-statmake>                     problem = "Not enough" if len(res) < len(tup_params) else "Too many"
python3.10-statmake>                     exc = ValueError(
python3.10-statmake>                         f"{problem} values in {obj!r} to structure as {tup!r}"
python3.10-statmake>                     )
python3.10-statmake>                     msg = f"Structuring {tup}"
python3.10-statmake>                     exc.__notes__ = getattr(e, "__notes__", []) + [msg]
python3.10-statmake>                     errors.append(exc)
python3.10-statmake>                 if errors:
python3.10-statmake>                     raise IterableValidationError(
python3.10-statmake>                         f"While structuring {tup!r}", errors, tup
python3.10-statmake>                     )
python3.10-statmake>                 return tuple(res)
python3.10-statmake>             else:
python3.10-statmake>                 res = tuple(
python3.10-statmake>                     [
python3.10-statmake>                         self._structure_func.dispatch(t)(e, t)
python3.10-statmake>                         for t, e in zip(tup_params, obj)
python3.10-statmake>                     ]
python3.10-statmake>                 )
python3.10-statmake>                 if len(res) < exp_len:
python3.10-statmake>                     problem = "Not enough" if len(res) < len(tup_params) else "Too many"
python3.10-statmake> >                   raise ValueError(
python3.10-statmake>                         f"{problem} values in {obj!r} to structure as {tup!r}"
python3.10-statmake>                     )
python3.10-statmake> E                   ValueError: Not enough values in [200] to structure as typing.Tuple[float, float]
python3.10-statmake> /nix/store/5mbdy53diadpqp390k3mpfn366b78ynm-python3.10-cattrs-22.2.0/lib/python3.10/site-packages/cattrs/converters.py:655: ValueError
python3.10-statmake> =========================== short test summary info ============================
python3.10-statmake> FAILED tests/test_make_stat.py::test_load_stylespace_broken_range - ValueErro...
python3.10-statmake> ========================= 1 failed, 28 passed in 0.67s =========================

the test is broken again

Artturin commented 1 year ago

https://github.com/python-attrs/cattrs/blob/main/HISTORY.rst#2220-2022-10-03

madig commented 1 year ago

@Artturin Try https://github.com/daltonmaag/statmake/releases/tag/v0.6.0.