Ejemplo n.º 1
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
Ejemplo n.º 2
0
def test_forbid_extra_keys_nested_override():
    @attr.s
    class C:
        a = attr.ib(type=int, default=1)

    @attr.s
    class A:
        c = attr.ib(type=C)
        a = attr.ib(type=int, default=2)

    converter = Converter(forbid_extra_keys=True)
    unstructured = {"a": 3, "c": {"a": 4}}
    # at this point, structuring should still work
    converter.structure(unstructured, A)
    # if we break it in the subclass, we need it to raise
    unstructured["c"]["aa"] = 5
    with pytest.raises(Exception):
        converter.structure(unstructured, A)
    # we can "fix" that by disabling forbid_extra_keys on the subclass
    hook = make_dict_structure_fn(C, converter, _cattr_forbid_extra_keys=False)
    converter.register_structure_hook(C, hook)
    converter.structure(unstructured, A)
    # but we should still raise at the top level
    unstructured["b"] = 6
    with pytest.raises(Exception):
        converter.structure(unstructured, A)
Ejemplo n.º 3
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
Ejemplo n.º 4
0
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, _ = 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:
        unstructured = converter.unstructure(inst)
        assert inst == converter.structure(
            converter.unstructure(unstructured), 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.register_structure_hook(Union[cl_a, cl_b], handler)
        unstructured = converter.unstructure(inst)
        assert inst == converter.structure(unstructured, C)
Ejemplo n.º 5
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)