예제 #1
0
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)
예제 #2
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)])
예제 #3
0
def test_no_linecache():
    """Linecaching should be disableable."""
    @define
    class A:
        a: int

    c = GenConverter()
    before = len(linecache.cache)
    c.structure(c.unstructure(A(1)), A)
    after = len(linecache.cache)

    assert after == before + 2

    @define
    class B:
        a: int

    before = len(linecache.cache)
    c.register_structure_hook(
        B, make_dict_structure_fn(B, c, _cattrs_use_linecache=False))
    c.register_unstructure_hook(
        B, make_dict_unstructure_fn(B, c, _cattrs_use_linecache=False))
    c.structure(c.unstructure(B(1)), B)

    assert len(linecache.cache) == before
예제 #4
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
예제 #5
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)
예제 #6
0
def register_hooks(conv: GenConverter, allow_bytes: bool = True) -> None:
    def attrs_hook_factory(cls: Type[Any],
                           gen_fn: Callable[..., Callable[[Any], Any]],
                           structuring: bool) -> Callable[[Any], Any]:
        base = get_origin(cls)
        if base is None:
            base = cls
        attribs = fields(base)
        # PEP563 postponed annotations need resolving as we check Attribute.type below
        resolve_types(base)
        kwargs: dict[str, bool | AttributeOverride] = {}
        if structuring:
            kwargs["_cattrs_forbid_extra_keys"] = conv.forbid_extra_keys
            kwargs[
                "_cattrs_prefer_attrib_converters"] = conv._prefer_attrib_converters
        else:
            kwargs["_cattrs_omit_if_default"] = conv.omit_if_default
        for a in attribs:
            if a.type in conv.type_overrides:
                # cattrs' gen_(un)structure_attrs_fromdict (used by default for attrs
                # classes that don't have a custom hook registered) check for any
                # type_overrides (Dict[Type, AttributeOverride]); they allow a custom
                # converter to omit specific attributes of given type e.g.:
                # >>> conv = GenConverter(type_overrides={Image: override(omit=True)})
                attrib_override = conv.type_overrides[a.type]
            else:
                # by default, we omit all Optional attributes (i.e. with None default),
                # overriding a Converter's global 'omit_if_default' option. Specific
                # attibutes can still define their own 'omit_if_default' behavior in
                # the Attribute.metadata dict.
                attrib_override = override(
                    omit_if_default=a.metadata.get("omit_if_default",
                                                   a.default is None or None),
                    rename=a.metadata.get(
                        "rename_attr",
                        a.name[1:] if a.name[0] == "_" else None),
                    omit=not a.init,
                )
            kwargs[a.name] = attrib_override

        return gen_fn(cls, conv, **kwargs)

    def custom_unstructure_hook_factory(
            cls: Type[Any]) -> Callable[[Any], Any]:
        return partial(cls._unstructure, converter=conv)

    def custom_structure_hook_factory(cls: Type[Any]) -> Callable[[Any], Any]:
        return partial(cls._structure, converter=conv)

    def unstructure_transform(t: Transform) -> Tuple[float]:
        return cast(Tuple[float], tuple(t))

    conv.register_unstructure_hook_factory(
        is_ufoLib2_attrs_class,
        partial(attrs_hook_factory,
                gen_fn=make_dict_unstructure_fn,
                structuring=False),
    )
    conv.register_unstructure_hook_factory(
        is_ufoLib2_class_with_custom_unstructure,
        custom_unstructure_hook_factory,
    )
    conv.register_unstructure_hook(cast(Type[Transform], Transform),
                                   unstructure_transform)

    conv.register_structure_hook_factory(
        is_ufoLib2_attrs_class,
        partial(attrs_hook_factory,
                gen_fn=make_dict_structure_fn,
                structuring=True),
    )
    conv.register_structure_hook_factory(
        is_ufoLib2_class_with_custom_structure,
        custom_structure_hook_factory,
    )

    if not allow_bytes:
        from base64 import b64decode, b64encode

        def unstructure_bytes(v: bytes) -> str:
            return (b64encode(v) if v else b"").decode("utf8")

        def structure_bytes(v: str, _: Any) -> bytes:
            return b64decode(v)

        conv.register_unstructure_hook(bytes, unstructure_bytes)
        conv.register_structure_hook(bytes, structure_bytes)