cattrs icon indicating copy to clipboard operation
cattrs copied to clipboard

Option to include `init=False` globally on a converter, not per class

Open MicaelJarniac opened this issue 1 year ago • 2 comments

  • cattrs version: v23.2.3
  • Python version: 3.11.6
  • Operating System: Ubuntu

Description

On previous cattrs versions, I used to structure and unstructure a lot of attrs classes that had many init=False fields.

Now, though, with the new versions of cattrs (v23.2.0), this behavior changed, breaking my code.

I'd like to be able to enable the old behavior back globally on a converter, instead of having to enable it for every single class, as I have many classes all over the place that would benefit from re-enabling init=False fields in conversion.

import attrs
import cattrs


@attrs.define
class Foo:
    bar: int
    baz: str = attrs.field(init=False)


foo = Foo(bar=1)
foo.baz = "hello"


converter = cattrs.Converter()
print(converter.unstructure(foo))
# before: {'bar': 1, 'baz': 'hello'}
# after: {'bar': 1}

I thought about something like this:

import cattrs
from cattrs.strategies import include_init_false

converter = cattrs.Converter()
include_init_false(converter)

# OR

converter = cattrs.Converter(include_init_false=True)

MicaelJarniac avatar Jan 10 '24 18:01 MicaelJarniac

I think I found a workaround on the docs, but it wasn't linked in the places I quoted:

  • https://catt.rs/en/stable/unstructuring.html#unstructuring-hook-factories

I'll try it out and see if it solves all my problems - I think it likely will.

But anyway, I think it'd be nice to link this workaround to these places:

  • https://catt.rs/en/stable/history.html#id4
  • https://catt.rs/en/latest/customizing.html#include-init-false

(Also note that the first link includes a broken reference to the second one, which would also be nice to fix.)

Edit: Just noticed that the example is for the opposite case - to exclude init=False fields. But by changing it a tiny bit it does work to do what I want.

Edit: I simplified the code from the example, using newer features, and made it into a function:

import cattrs
from attrs import has
from cattrs.gen import make_dict_unstructure_fn


def include_init_false(c: cattrs.Converter) -> None:
    c.register_unstructure_hook_factory(
        has,
        lambda cl: make_dict_unstructure_fn(
            cl=cl,
            converter=c,
            _cattrs_include_init_false=True,
        ),
    )

MicaelJarniac avatar Jan 10 '24 19:01 MicaelJarniac

Howdy!

I think a converter parameter is almost certainly not the way to go, even though I've went that route before. Simply because the converter will end up a huge mess of different parameters.

Also the point of cattrs is to customize it with hooks and hook factories ;) I think your solution is pretty good.

Documentation is good though. I've been playing around with the idea of creating a migrations page in the docs. This would be a collection of recipes for dealing with breaking changes in cattrs, per version. And it would be in parallel with the changelog, just a different page. The idea being, if you're upgrading cattrs you can just look here for recipes on how to restore old behavior. I think this would be a good candidate for something like that.

Tinche avatar Jan 10 '24 22:01 Tinche