python-attrs / python-attrs/cattrs

Using several factories simultaneously

Open
#659 7 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
1.1k
Forks
159
Avg merge
12h 21m
Merged PRs (30d)
6

Description

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 🙃

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the example and tracing register_unstructure_hook_factory, unstructure, and make_dict_unstructure_fn to understand how predicates and factories are selected. Determine whether the requested composition is supported by the existing API or needs a design decision. Done means Base, Sub, and SubSub produce the three expected dictionaries with type, is_sub, and renamed fields applied together.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.