def test_calling_back():
    """Calling unstructure_attrs_asdict from a hook should not override a manual hook."""
    converter = Converter()

    @attr.define
    class C:
        a: int = attr.ib(default=1)

    def handler(obj):
        return {
            "type_tag": obj.__class__.__name__,
            **converter.unstructure_attrs_asdict(obj),
        }

    converter.register_unstructure_hook(C, handler)

    inst = C()

    expected = {"type_tag": "C", "a": 1}

    unstructured1 = converter.unstructure(inst)
    unstructured2 = converter.unstructure(inst)

    assert unstructured1 == expected, repr(unstructured1)
    assert unstructured2 == unstructured1, repr(unstructured2)
def test_simple_roundtrip(cls_and_vals, strat):
    """
    Simple classes with metadata can be unstructured and restructured.
    """
    converter = Converter(unstruct_strat=strat)
    cl, vals = cls_and_vals
    inst = cl(*vals)
    assert inst == converter.structure(converter.unstructure(inst), cl)
Exemple #3
0
def test_simple_recursive():
    c = GenConverter()

    orig = A([A([])])
    unstructured = c.unstructure(orig)

    assert unstructured == {"inner": [{"inner": []}]}

    assert c.structure(unstructured, A) == orig
def test_nested_roundtrip(cls_and_vals, strat):
    """
    Nested classes with metadata can be unstructured and restructured.
    """
    converter = Converter(unstruct_strat=strat)
    cl, vals = cls_and_vals
    # Vals are a tuple, convert into a dictionary.
    inst = cl(*vals)
    assert inst == converter.structure(converter.unstructure(inst), cl)
Exemple #5
0
def test_simple_roundtrip_with_extra_keys_forbidden(cls_and_vals, strat):
    """
    Simple classes can be unstructured and restructured with forbid_extra_keys=True.
    """
    converter = Converter(unstruct_strat=strat, forbid_extra_keys=True)
    cl, vals = cls_and_vals
    inst = cl(*vals)
    unstructured = converter.unstructure(inst)
    assert "Hyp" not in repr(unstructured)
    assert inst == converter.structure(unstructured, cl)
def test_union_field_roundtrip(cl_and_vals_a, cl_and_vals_b, strat):
    """
    Classes with union fields can be unstructured and structured.
    """
    converter = Converter(unstruct_strat=strat)
    cl_a, vals_a = cl_and_vals_a
    cl_b, vals_b = cl_and_vals_b
    a_field_names = {a.name for a in fields(cl_a)}
    b_field_names = {a.name for a in fields(cl_b)}
    assume(a_field_names)
    assume(b_field_names)

    common_names = a_field_names & b_field_names
    assume(len(a_field_names) > len(common_names))

    @attr.s
    class C(object):
        a = attr.ib(type=Union[cl_a, cl_b])

    inst = C(a=cl_a(*vals_a))

    if strat is UnstructureStrategy.AS_DICT:
        assert inst == converter.structure(converter.unstructure(inst), C)
    else:
        # Our disambiguation functions only support dictionaries for now.
        with pytest.raises(ValueError):
            converter.structure(converter.unstructure(inst), C)

        def handler(obj, _):
            return converter.structure(obj, cl_a)

        converter._union_registry[Union[cl_a, cl_b]] = handler
        assert inst == converter.structure(converter.unstructure(inst), C)
        del converter._union_registry[Union[cl_a, cl_b]]
Exemple #7
0
def test_structure_linecache():
    """Linecaching for structuring should work."""
    @define
    class A:
        a: int

    c = GenConverter()
    try:
        c.structure({"a": "test"}, A)
    except ValueError:
        res = format_exc()
        assert "'a'" in res
Exemple #8
0
def test_forbid_extra_keys_defaults(attr_and_vals):
    """
    Restructuring fails when a dict key is renamed (if forbid_extra_keys set)
    """
    a, _ = attr_and_vals
    cl = make_class("HypClass", {"a": a})
    converter = Converter(forbid_extra_keys=True)
    inst = cl()
    unstructured = converter.unstructure(inst)
    unstructured["aa"] = unstructured.pop("a")
    with pytest.raises(Exception):
        converter.structure(unstructured, cl)
Exemple #9
0
 def _structure(data: dict[str, Any], cls: Type[Layer],
                converter: GenConverter) -> Layer:
     return cls(
         name=data.get("name", DEFAULT_LAYER_NAME),
         glyphs={
             k: converter.structure(v, Glyph)
             for k, v in data.get("glyphs", {}).items()
         },
         color=data.get("color"),
         lib=converter.structure(data.get("lib", {}), Lib),
         default=data.get("default", False),
     )
Exemple #10
0
def test_forbid_extra_keys(cls_and_vals):
    """
    Restructuring fails when extra keys are present (when configured)
    """
    converter = Converter(forbid_extra_keys=True)
    cl, vals = cls_and_vals
    inst = cl(*vals)
    unstructured = converter.unstructure(inst)
    bad_key = list(unstructured)[0] + "A" if unstructured else "Hyp"
    while bad_key in unstructured:
        bad_key += "A"
    unstructured[bad_key] = 1
    with pytest.raises(Exception):
        converter.structure(unstructured, cl)
Exemple #11
0
def test_unstructure_linecache():
    """Linecaching for unstructuring should work."""
    @define
    class Inner:
        a: int

    @define
    class Outer:
        inner: Inner

    c = GenConverter()
    try:
        c.unstructure(Outer({}))
    except AttributeError:
        res = format_exc()
        assert "'a'" in res
Exemple #12
0
def _unstructure_data(value: Any, converter: GenConverter) -> Any:
    if isinstance(value, bytes):
        return {"type": DATA_LIB_KEY, "data": converter.unstructure(value)}
    elif isinstance(value, (list, tuple)):
        return [_unstructure_data(v, converter) for v in value]
    elif isinstance(value, Mapping):
        return {k: _unstructure_data(v, converter) for k, v in value.items()}
    return value
Exemple #13
0
def test_annotated_attrs():
    """Annotation support works for attrs classes."""
    from typing import Annotated

    converter = Converter()

    @attr.define
    class Inner:
        a: int

    @attr.define
    class Outer:
        i: Annotated[Inner, "test"]
        j: list[Annotated[Inner, "test"]]

    orig = Outer(Inner(1), [Inner(1)])
    raw = converter.unstructure(orig)

    assert raw == {"i": {"a": 1}, "j": [{"a": 1}]}

    structured = converter.structure(raw, Outer)
    assert structured == orig

    # Now register a hook and rerun the test.
    converter.register_unstructure_hook(Inner, lambda v: {"a": 2})

    raw = converter.unstructure(Outer(Inner(1), [Inner(1)]))

    assert raw == {"i": {"a": 2}, "j": [{"a": 2}]}

    structured = converter.structure(raw, Outer)
    assert structured == Outer(Inner(2), [Inner(2)])
Exemple #14
0
def test_optional_field_roundtrip(cl_and_vals):
    """
    Classes with optional fields can be unstructured and structured.
    """
    converter = Converter()
    cl, vals = cl_and_vals

    @attr.s
    class C(object):
        a = attr.ib(type=Optional[cl])

    inst = C(a=cl(*vals))
    assert inst == converter.structure(converter.unstructure(inst), C)

    inst = C(a=None)
    unstructured = converter.unstructure(inst)

    assert inst == converter.structure(unstructured, C)
Exemple #15
0
def test_type_overrides(cl_and_vals):
    """
    Type overrides on the GenConverter work.
    """
    converter = Converter(type_overrides={int: override(omit_if_default=True)})
    cl, vals = cl_and_vals

    inst = cl(*vals)
    unstructured = converter.unstructure(inst)

    for field, val in zip(fields(cl), vals):
        if field.type is int:
            if field.default is not None:
                if isinstance(field.default, Factory):
                    if not field.default.takes_self and field.default() == val:
                        assert field.name not in unstructured
                elif field.default == val:
                    assert field.name not in unstructured
Exemple #16
0
    def _unstructure(self, converter: GenConverter) -> dict[str, Any]:
        # avoid encoding if converter supports bytes natively
        test = converter.unstructure(b"\0")
        if isinstance(test, bytes):
            return dict(self)
        elif not isinstance(test, str):
            raise NotImplementedError(type(test))

        data: dict[str, Any] = _unstructure_data(self, converter)
        return data
Exemple #17
0
    def _unstructure(self, converter: GenConverter) -> dict[str, str]:
        # avoid encoding if converter supports bytes natively
        test = converter.unstructure(b"\0")
        if isinstance(test, bytes):
            # mypy complains that 'Argument 1 to "dict" has incompatible type
            # "DataStore"; expected "SupportsKeysAndGetItem[str, Dict[str, str]]"'.
            # We _are_ a subclass of Mapping so we do support keys and getitem...
            return dict(self)  # type: ignore
        elif not isinstance(test, str):
            raise NotImplementedError(type(test))

        data: dict[str, str] = {
            k: converter.unstructure(v)
            for k, v in self.items()
        }
        # since we unpacked all data by now, we're no longer lazy
        if self._lazy:
            self._lazy = False
        return data
Exemple #18
0
def test_seq_of_simple_classes_unstructure(cls_and_vals,
                                           seq_type_and_annotation):
    """Dumping a sequence of primitives is a simple copy operation."""
    converter = Converter()

    test_val = ("test", 1)

    for cl, _ in cls_and_vals:
        converter.register_unstructure_hook(cl, lambda _: test_val)
        break  # Just register the first class.

    seq_type, annotation = seq_type_and_annotation
    inputs = seq_type(cl(*vals) for cl, vals in cls_and_vals)
    outputs = converter.unstructure(
        inputs,
        unstructure_as=annotation[cl]
        if annotation not in (Tuple, tuple) else annotation[cl, ...],
    )
    assert all(e == test_val for e in outputs)
Exemple #19
0
def _structure_data_inplace(key: Union[int, str], value: Any, container: Any,
                            converter: GenConverter) -> None:
    if isinstance(value, list):
        for i, v in enumerate(value):
            _structure_data_inplace(i, v, value, converter)
    elif is_data_dict(value):
        container[key] = converter.structure(value["data"], bytes)
    elif isinstance(value, Mapping):
        for k, v in value.items():
            _structure_data_inplace(k, v, value, converter)
Exemple #20
0
def test_39_structure_generics_with_cols(t, result):
    @define
    class GenericCols(Generic[T]):
        a: T
        b: list[T]
        c: dict[str, T]

    expected = GenericCols(*result)

    res = GenConverter().structure(asdict(expected), GenericCols[t])

    assert res == expected
Exemple #21
0
def test_omit_default_roundtrip(cl_and_vals):
    """
    Omit default on the converter works.
    """
    converter = Converter(omit_if_default=True)
    cl, vals = cl_and_vals

    @attr.s
    class C(object):
        a: int = attr.ib(default=1)
        b: cl = attr.ib(factory=lambda: cl(*vals))

    inst = C()
    unstructured = converter.unstructure(inst)
    assert unstructured == {}
    assert inst == converter.structure(unstructured, C)

    inst = C(0)
    unstructured = converter.unstructure(inst)
    assert unstructured == {"a": 0}
    assert inst == converter.structure(unstructured, C)
Exemple #22
0
def test_unstructure_generic_attrs():
    c = GenConverter()

    @attrs(auto_attribs=True)
    class Inner(Generic[T]):
        a: T

    @attrs(auto_attribs=True)
    class Outer:
        inner: Inner[int]

    initial = Outer(Inner(1))
    raw = c.unstructure(initial)

    assert raw == {"inner": {"a": 1}}

    new = c.structure(raw, Outer)
    assert initial == new

    @attrs(auto_attribs=True)
    class OuterStr:
        inner: Inner[str]

    assert c.structure(raw, OuterStr) == OuterStr(Inner("1"))
Exemple #23
0
 def _structure(
     data: Mapping[str, Any],
     cls: Type[DataStore],
     converter: GenConverter,
 ) -> DataStore:
     self = cls()
     for k, v in data.items():
         if isinstance(v, str):
             self[k] = converter.structure(v, bytes)
         elif isinstance(v, bytes):
             self[k] = v
         else:
             raise TypeError(
                 f"Expected (base64) str or bytes, found: {type(v).__name__!r}"
             )
     return self
Exemple #24
0
def test_simple_roundtrip_defaults(cls_and_vals, strat):
    """
    Simple classes with metadata can be unstructured and restructured.
    """
    a, _ = cls_and_vals
    cl = make_class("HypClass", {"a": a})
    converter = Converter(unstruct_strat=strat)
    inst = cl()
    assert converter.unstructure(converter.structure(
        {}, cl)) == converter.unstructure(inst)
    assert inst == converter.structure(converter.unstructure(inst), cl)
Exemple #25
0
def test_overriding_generated_unstructure():
    """Test overriding a generated unstructure hook works."""
    converter = Converter()

    @attr.define
    class Inner:
        a: int

    @attr.define
    class Outer:
        i: Inner

    inst = Outer(Inner(1))
    converter.unstructure(inst)

    converter.register_unstructure_hook(Inner, lambda _: {"a": 2})

    r = converter.structure(converter.unstructure(inst), Outer)
    assert r.i.a == 2
Exemple #26
0
def test_overriding_generated_structure():
    """Test overriding a generated structure hook works."""
    converter = Converter()

    @attr.define
    class Inner:
        a: int

    @attr.define
    class Outer:
        i: Inner

    inst = Outer(Inner(1))
    raw = converter.unstructure(inst)
    converter.structure(raw, Outer)

    converter.register_structure_hook(Inner, lambda p, _: Inner(p["a"] + 1))

    r = converter.structure(raw, Outer)
    assert r.i.a == 2
Exemple #27
0
 def _unstructure(self, converter: GenConverter) -> dict[str, Any]:
     # omit glyph name attribute, already used as key
     glyphs: dict[str, dict[str, Any]] = {}
     for glyph_name in self._glyphs:
         g = converter.unstructure(self[glyph_name])
         assert glyph_name == g.pop("name")
         glyphs[glyph_name] = g
     d: dict[str, Any] = {
         # never omit name even if == 'public.default' as that acts as
         # the layer's "key" in the layerSet.
         "name": self._name,
     }
     default: Any
     for key, value, default in [
         ("default", self._default, self._name == DEFAULT_LAYER_NAME),
         ("glyphs", glyphs, {}),
         ("lib", self._lib, {}),
     ]:
         if not converter.omit_if_default or value != default:
             d[key] = value
     if self.color is not None:
         d["color"] = self.color
     return d
def test_collection_unstructure_override_seq():
    """Test overriding unstructuring seq."""

    # First approach, predicate hook
    c = GenConverter()

    c._unstructure_func.register_func_list([(
        is_sequence,
        partial(c.gen_unstructure_iterable, unstructure_to=tuple),
        True,
    )])

    assert c.unstructure([1, 2, 3], unstructure_as=Sequence[int]) == (1, 2, 3)

    @attr.define
    class MyList:
        args = attr.ib(converter=list)

    # Second approach, using abc.MutableSequence
    c = GenConverter(unstruct_collection_overrides={MutableSequence: MyList})

    assert c.unstructure([1, 2, 3], unstructure_as=Sequence[int]) == [1, 2, 3]
    assert c.unstructure([1, 2, 3],
                         unstructure_as=MutableSequence[int]) == MyList([
                             1,
                             2,
                             3,
                         ])
    assert c.unstructure([1, 2, 3]) == MyList([
        1,
        2,
        3,
    ])
    assert c.unstructure((1, 2, 3)) == [
        1,
        2,
        3,
    ]

    # Second approach, using abc.Sequence
    c = GenConverter(unstruct_collection_overrides={Sequence: MyList})

    assert c.unstructure([1, 2, 3],
                         unstructure_as=Sequence[int]) == MyList([1, 2, 3])
    assert c.unstructure(
        [1, 2, 3], unstructure_as=MutableSequence[int]) == MyList([1, 2, 3])

    assert c.unstructure([1, 2, 3]) == MyList([1, 2, 3])

    assert c.unstructure((1, 2, 3), unstructure_as=tuple[int, ...]) == MyList([
        1,
        2,
        3,
    ])

    # Second approach, using __builtins__.list
    c = GenConverter(unstruct_collection_overrides={list: MyList})

    assert c.unstructure([1, 2, 3], unstructure_as=Sequence[int]) == [1, 2, 3]
    assert c.unstructure([1, 2, 3], unstructure_as=MutableSequence[int]) == [
        1,
        2,
        3,
    ]
    assert c.unstructure([1, 2, 3]) == MyList([
        1,
        2,
        3,
    ])
    assert c.unstructure((1, 2, 3)) == [
        1,
        2,
        3,
    ]

    # Second approach, using __builtins__.tuple
    c = GenConverter(unstruct_collection_overrides={tuple: MyList})

    assert c.unstructure([1, 2, 3], unstructure_as=Sequence[int]) == [1, 2, 3]
    assert c.unstructure([1, 2, 3], unstructure_as=MutableSequence[int]) == [
        1,
        2,
        3,
    ]
    assert c.unstructure([1, 2, 3]) == [
        1,
        2,
        3,
    ]
    assert c.unstructure((1, 2, 3)) == MyList([
        1,
        2,
        3,
    ])
Exemple #29
0
 def _structure(data: list[dict[str, Any]], cls: Type[LayerSet],
                converter: GenConverter) -> LayerSet:
     return cls.from_iterable(
         converter.structure(layer, Layer) for layer in data)
Exemple #30
0
 def _unstructure(self, converter: GenConverter) -> list[dict[str, Any]]:
     return [converter.unstructure(layer) for layer in self]