cattrs icon indicating copy to clipboard operation
cattrs copied to clipboard

Using several factories simultaneously

Open AdrianSosic opened this issue 4 months ago • 2 comments

Hi @Tinche, my previous issue was basically pointless but I'm now getting closer to the heart of my problem. While I think I understand how regular hooks can be composed, it's not quite clear to me how somethign similar can be achieved using predicates/factories.

In essence, it would help me to better understand how hooks/predicates/factories interplay and how it is possible to "activate" certain customizations at once. The problem that I get with factories is that only ever one of them is active. Here a little example what I mean:

import cattrs
from attrs import define

converter = cattrs.Converter()


@define
class Base:
    x: int = 0


@define
class Sub(Base):
    y: int = 1


@define
class SubSub(Sub):
    z: int = 1


@define
class Container:
    contains: Base


@converter.register_unstructure_hook
def some_special_configuration(obj: SubSub) -> dict:
    fn = cattrs.gen.make_dict_unstructure_fn(
        SubSub, converter, x=cattrs.override(rename="renamed")
    )
    return fn(obj)


@converter.register_unstructure_hook_factory(lambda c: c is Base)
def add_type(_):
    def hook(obj):
        hook = cattrs.gen.make_dict_unstructure_fn(type(obj), converter)
        return {"type": obj.__class__.__name__, **hook(obj)}

    return hook


@converter.register_unstructure_hook_factory(lambda c: issubclass(c, Sub))
def indicate_if_sub(_):
    def hook(obj):
        hook = cattrs.gen.make_dict_unstructure_fn(type(obj), converter)
        return {"is_sub": obj.__class__.__name__, **hook(obj)}

    return hook


contains_base = Container(Base())
contains_sub = Container(Sub())
contains_subsub = Container(SubSub())

print(converter.unstructure(contains_base))
print(converter.unstructure(contains_sub))
print(converter.unstructure(contains_subsub))

Gives:

{'contains': {'type': 'Base', 'x': 0}}
{'contains': {'type': 'Sub', 'x': 0, 'y': 1}}
{'contains': {'type': 'SubSub', 'x': 0, 'y': 1, 'z': 1}}

How can I change the logic such that all rules are "active" at the same time, that is:

  • Because everything is unstructured as Base, the type field should be always present.
  • Both sub and subsub are of type Sub, meaning I want to get their additional is_sub field.
  • Finally, subsub has a special renaming that should be considered.

So what I want is rather:

{'contains': {'type': 'Base', 'x': 0}}
{'contains': {'type': 'Sub', 'is_sub': 'Sub', 'x': 0, 'y': 1}}
{'contains': {'type': 'SubSub', 'is_sub': 'SubSub', 'renamed': 0, 'y': 1, 'z': 1}}

I'd really appreciate if you could point me to the right track 🙃

AdrianSosic avatar Jun 18 '25 14:06 AdrianSosic