Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GenConverter fails to structure when init=False attributes are present #166

Closed
bushkov opened this issue Aug 27, 2021 · 14 comments
Closed

Comments

@bushkov
Copy link

bushkov commented Aug 27, 2021

  • cattrs version: 1.8.0
  • Python version: 3.8.11
  • Operating System: macOS 11.5.2

Description

I am trying to structure an attrs class that contains an attribute which shouldn't be initialized on instance creation. When I use Converter, the structuring works fine, but when I use GenConverter it fails.

What I Did

Here is a simple example that fails:

import attr
import cattr


c = cattr.GenConverter()

@attr.s
class A:
    x: int = attr.ib()
    y: int = attr.ib(init=False)


d = {"x": 0}

c.structure(d, A)

It fails with:

Traceback (most recent call last):
  File "example.py", line 15, in <module>
    c.structure(d, A)
  File "cattr/converters.py", line 294, in structure
    return self._structure_func.dispatch(cl)(obj, cl)
  File "<cattrs generated structure __main__.A>", line 4, in structure_A
KeyError: 'y'

On a related note it's not clear to me from the documentation in which situation one should prefer to use Converter and in which situation one should prefer GenConverter. In particular the following points are unclear:

GenConverter differs from the old cattr.Converter in the following ways:

  • structuring and unstructuring of attrs classes is slower the first time, but faster every subsequent time
  • structuring and unstructuring can be customized
  • support for easy overriding collection unstructuring

By 'faster every subsequent time' does it mean that GenConverter is faster than Converter or does it mean that it's getting faster with every structure/unstructure, but not faster than Converter?

About 'structuring and unstructuring can be customized' and support for easy overriding collection unstructuring, it would be helpful to have some concrete examples showing what one can do with GenConverter which cannot be done with Converter.

@Tinche
Copy link
Member

Tinche commented Aug 27, 2021

Hello.

init=False attributes are kinda weird in a serialization and deserialization context. I don't really know what the right approach would be when de/serializing them, so the code doesn't deal with them at all. The fact the Converter works and GenConverter doesn't is simply due to the differences in how they work, it's not intentional. For that reason, I would advise to not use these fields in classes you're de/serializing. If you still want to use them you need to specify the behavior yourself, and I can help with that (it would involve hook factories, with which you're familiar).

To answer your other questions. GenConverter will basically write an un/structuring function for you. So the first time it sees a class, this functions needs to be generated, and that's a little slow. But then this function gets cached and reused. Now, this function is essentially what you'd write yourself, so it's basically the fastest it can possibly be in pure Python.

Converter is basically a legacy component which I don't plan on deprecating since it has users. The difference is it doesn't generate these functions, but always uses a prewritten handler. The prewritten handler is significantly slower (how much depends on the number of fields, mostly). Generally we're still talking single or double digit microseconds, so you might not care.

For overriding collection unstructuring, the docs are here: https://cattrs.readthedocs.io/en/latest/unstructuring.html#customizing-collection-unstructuring

@Tinche
Copy link
Member

Tinche commented Sep 1, 2021

Ok to close this?

@bushkov
Copy link
Author

bushkov commented Sep 7, 2021

@Tinche yes, it's ok to close this one.

@Tinche Tinche closed this as completed Sep 9, 2021
@artem30801
Copy link

Hello! What's the best workaround here? Using override(omit=True) doesn't seem to work

@Tinche
Copy link
Member

Tinche commented Oct 12, 2021

@artem30801 omit=True not working is news to me. Feel free to show an example

@artem30801
Copy link

artem30801 commented Oct 12, 2021

That's a minimal (I think) example of my problem, @Tinche:

import attr
import cattr
from cattr.gen import make_dict_structure_fn, override


@attr.define()
class A:
    name: str = attr.field(init=False)
    data: str = attr.field()


converter = cattr.GenConverter()


def structure(cls):
    overrides = {a.name: override(omit=True) for a in attr.fields(cls) if not a.init}
    return make_dict_structure_fn(cls, converter, **overrides)


converter.register_structure_hook_factory(attr.has, structure)

d = {"data": "value"}
converter.structure(d, A)

Traceback when I run the code:

Traceback (most recent call last):
  File "xxx\test.py", line 23, in <module>
    converter.structure(d, A)
  File "xxx\.venv\lib\site-packages\cattr\converters.py", line 294, in structure
    return self._structure_func.dispatch(cl)(obj, cl)
  File "<cattrs generated structure __main__.A>", line 3, in structure_A
KeyError: 'name'

I think that code shoud omit name field, though it doesn't seem to work

Edit:

converter.register_structure_hook(A, make_dict_structure_fn(A, converter, name=override(omit=False)))

Yields same results

@Tinche
Copy link
Member

Tinche commented Oct 12, 2021

@artem30801 You're right, it doesn't work for structuring. It's even documented here: https://cattrs.readthedocs.io/en/latest/customizing.html#omit

That said, I don't remember why it doesn't work for structuring in the first place. I guess no one asked for it yet (and I misunderstood the OP). I can add it in the next couple of days.

@Tinche Tinche reopened this Oct 12, 2021
@Tinche
Copy link
Member

Tinche commented Oct 14, 2021

@artem30801 The master branch has support for structuring omit. Feel free to give it a test to see if it serves your needs

@artem30801
Copy link

Thank you very much!
Tested out master branch, it seems to work without any issues for my cases
Also allows you to have fields with recursive type hints now (excluded from init of course).

@otaj
Copy link

otaj commented Dec 3, 2021

Hi @Tinche , would it be possible to release a new version with this fix released? Thanks!

@otaj
Copy link

otaj commented Dec 3, 2021

I just noticed, that there already is a version of 1.9.0, just not yet released to PyPI. Can you please release it there? Thanks!

@Tinche
Copy link
Member

Tinche commented Dec 3, 2021

1.9.0 hasn't been released yet at all. I can get around to it over the weekend.

@otaj
Copy link

otaj commented Dec 3, 2021

Oh, right, it just has a version in pyproject.toml. If you could release it over the weekend (or just some minor version with patch for this included), that would be awesome! Thank you!

@Tinche
Copy link
Member

Tinche commented Dec 6, 2021

@otaj Released!

@Tinche Tinche closed this as completed Dec 6, 2021
mergify bot pushed a commit to aws/jsii that referenced this issue Jan 6, 2022
…1 in /packages/@jsii/python-runtime (#3315)

Updates the requirements on [cattrs](https://github.com/python-attrs/cattrs) to permit the latest version.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/python-attrs/cattrs/blob/main/HISTORY.rst">cattrs's changelog</a>.</em></p>
<blockquote>
<h2>1.10.0 (2022-01-04)</h2>
<ul>
<li>Add PEP 563 (string annotations) support for dataclasses.
(<code>[#195](python-attrs/cattrs#195) &lt;https://github.com/python-attrs/cattrs/issues/195&gt;</code>_)</li>
<li>Fix handling of dictionaries with string Enum keys for bson, orjson, and tomlkit.</li>
<li>Rename the <code>cattr.gen.make_dict_unstructure_fn.omit_if_default</code> parameter to <code>_cattrs_omit_if_default</code>, for consistency. The <code>omit_if_default</code> parameters to <code>GenConverter</code> and <code>override</code> are unchanged.</li>
<li>Following the changes in <code>attrs</code> 21.3.0, add a <code>cattrs</code> package mirroring the existing <code>cattr</code> package. Both package names may be used as desired, and the <code>cattr</code> package isn't going away.</li>
</ul>
<h2>1.9.0 (2021-12-06)</h2>
<ul>
<li>Python 3.10 support, including support for the new union syntax (<code>A | B</code> vs <code>Union[A, B]</code>).</li>
<li>The <code>GenConverter</code> can now properly structure generic classes with generic collection fields.
(<code>[#149](python-attrs/cattrs#149) &lt;https://github.com/python-attrs/cattrs/issues/149&gt;</code>_)</li>
<li><code>omit=True</code> now also affects generated structuring functions.
(<code>[#166](python-attrs/cattrs#166) &lt;https://github.com/python-attrs/cattrs/issues/166&gt;</code>_)</li>
<li><code>cattr.gen.{make_dict_structure_fn, make_dict_unstructure_fn}</code> now resolve type annotations automatically when PEP 563 is used.
(<code>[#169](python-attrs/cattrs#169) &lt;https://github.com/python-attrs/cattrs/issues/169&gt;</code>_)</li>
<li>Protocols are now unstructured as their runtime types.
(<code>[#177](python-attrs/cattrs#177) &lt;https://github.com/python-attrs/cattrs/pull/177&gt;</code>_)</li>
<li>Fix an issue generating structuring functions with renaming and <code>_cattrs_forbid_extra_keys=True</code>.
(<code>[#190](python-attrs/cattrs#190) &lt;https://github.com/python-attrs/cattrs/issues/190&gt;</code>_)</li>
</ul>
<h2>1.8.0 (2021-08-13)</h2>
<ul>
<li>Fix <code>GenConverter</code> mapping structuring for unannotated dicts on Python 3.8.
(<code>[#151](python-attrs/cattrs#151) &lt;https://github.com/python-attrs/cattrs/issues/151&gt;</code>_)</li>
<li>The source code for generated un/structuring functions is stored in the <code>linecache</code> cache, which enables more informative stack traces when un/structuring errors happen using the <code>GenConverter</code>. This behavior can optionally be disabled to save memory.</li>
<li>Support using the attr converter callback during structure.
By default, this is a method of last resort, but it can be elevated to the default by setting <code>prefer_attrib_converters=True</code> on <code>Converter</code> or <code>GenConverter</code>.
(<code>[#138](python-attrs/cattrs#138) &lt;https://github.com/python-attrs/cattrs/issues/138&gt;</code>_)</li>
<li>Fix structuring recursive classes.
(<code>[#159](python-attrs/cattrs#159) &lt;https://github.com/python-attrs/cattrs/issues/159&gt;</code>_)</li>
<li>Converters now support un/structuring hook factories. This is the most powerful and complex venue for customizing un/structuring. This had previously been an internal feature.</li>
<li>The <code>Common Usage Examples &lt;https://cattrs.readthedocs.io/en/latest/usage.html#using-factory-hooks&gt;</code>_ documentation page now has a section on advanced hook factory usage.</li>
<li><code>cattr.override</code> now supports the <code>omit</code> parameter, which makes <code>cattrs</code> skip the atribute entirely when unstructuring.</li>
<li>The <code>cattr.preconf.bson</code> module is now tested against the <code>bson</code> module bundled with the <code>pymongo</code> package, because that package is much more popular than the standalone PyPI <code>bson</code> package.</li>
</ul>
<h2>1.7.1 (2021-05-28)</h2>
<ul>
<li><code>Literal</code> s are not supported on Python 3.9.0 (supported on 3.9.1 and later), so we skip importing them there.
(<code>[#150](python-attrs/cattrs#150) &lt;https://github.com/python-attrs/cattrs/issues/150&gt;</code>_)</li>
</ul>
<h2>1.7.0 (2021-05-26)</h2>
<ul>
<li><code>cattr.global_converter</code> (which provides <code>cattr.unstructure</code>, <code>cattr.structure</code> etc.) is now an instance of <code>cattr.GenConverter</code>.</li>
<li><code>Literal</code> s are now supported and validated when structuring.</li>
<li>Fix dependency metadata information for <code>attrs</code>.
(<code>[#147](python-attrs/cattrs#147) &lt;https://github.com/python-attrs/cattrs/issues/147&gt;</code>_)</li>
<li>Fix <code>GenConverter</code> mapping structuring for unannotated dicts.
(<code>[#148](python-attrs/cattrs#148) &lt;https://github.com/python-attrs/cattrs/issues/148&gt;</code>_)</li>
</ul>

</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/python-attrs/cattrs/commit/7d3a6ba5e0df942391349e332cb87f6871088015"><code>7d3a6ba</code></a> Bump to 1.10.0</li>
<li><a href="https://github.com/python-attrs/cattrs/commit/22b24c28fbeb2b8ca90d568dc4939bdc98ec5902"><code>22b24c2</code></a> Tin/import cattrs (<a href="https://github-redirect.dependabot.com/python-attrs/cattrs/issues/203">#203</a>)</li>
<li><a href="https://github.com/python-attrs/cattrs/commit/a0e56f43f061c43814d6f938833d1c325ed61525"><code>a0e56f4</code></a> Fix test with 32-bit time_t</li>
<li><a href="https://github.com/python-attrs/cattrs/commit/bc9432e606177fe10aa3d2d11e715970c92526be"><code>bc9432e</code></a> Documentation tweaks</li>
<li><a href="https://github.com/python-attrs/cattrs/commit/6260c58aa185200a08c76ecc99a941a26a93eeb8"><code>6260c58</code></a> Rename gen.make_dict_unstructure_fn.omit_if_default</li>
<li><a href="https://github.com/python-attrs/cattrs/commit/bb4383c2d97aae0e8a01db64f142d07350861a17"><code>bb4383c</code></a> Remove walrus</li>
<li><a href="https://github.com/python-attrs/cattrs/commit/81d7756541129a73b82f076b860d61c550296666"><code>81d7756</code></a> Clean up test</li>
<li><a href="https://github.com/python-attrs/cattrs/commit/de16200c3d02d259d04960ab466f49fcf8fa47a4"><code>de16200</code></a> Fix preconf string Enum keys</li>
<li><a href="https://github.com/python-attrs/cattrs/commit/077c9ea8521372f706346f1f39101db716ca3089"><code>077c9ea</code></a> CI tweak</li>
<li><a href="https://github.com/python-attrs/cattrs/commit/3ad74d4456c19598e7278deef62e3af85eabc99d"><code>3ad74d4</code></a> setup.cfg B gone</li>
<li>Additional commits viewable in <a href="https://github.com/python-attrs/cattrs/compare/v1.8.0...v1.10.0">compare view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
mergify bot pushed a commit to aws/jsii that referenced this issue Apr 4, 2022
…2 in /packages/@jsii/python-runtime (#3470)

Updates the requirements on [cattrs](https://github.com/python-attrs/cattrs) to permit the latest version.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/python-attrs/cattrs/blob/main/HISTORY.rst">cattrs's changelog</a>.</em></p>
<blockquote>
<h2>22.1.0 (2022-04-03)</h2>
<ul>
<li>cattrs now uses the CalVer versioning convention.</li>
<li>cattrs now has a detailed validation mode, which is enabled by default. Learn more <code>here &lt;https://cattrs.readthedocs.io/en/latest/validation.html&gt;</code>_.
The old behavior can be restored by creating the converter with <code>detailed_validation=False</code>.</li>
<li><code>attrs</code> and dataclass structuring is now ~25% faster.</li>
<li>Fix an issue structuring bare <code>typing.List</code> s on Pythons lower than 3.9.
(<code>[#209](python-attrs/cattrs#209) &lt;https://github.com/python-attrs/cattrs/issues/209&gt;</code>_)</li>
<li>Fix structuring of non-parametrized containers like <code>list/dict/...</code> on Pythons lower than 3.9.
(<code>[#218](python-attrs/cattrs#218) &lt;https://github.com/python-attrs/cattrs/issues/218&gt;</code>_)</li>
<li>Fix structuring bare <code>typing.Tuple</code> on Pythons lower than 3.9.
(<code>[#218](python-attrs/cattrs#218) &lt;https://github.com/python-attrs/cattrs/issues/218&gt;</code>_)</li>
<li>Fix a wrong <code>AttributeError</code> of an missing <code>__parameters__</code> attribute. This could happen
when inheriting certain generic classes – for example <code>typing.*</code> classes are affected.
(<code>[#217](python-attrs/cattrs#217) &lt;https://github.com/python-attrs/cattrs/issues/217&gt;</code>_)</li>
<li>Fix structuring of <code>enum.Enum</code> instances in <code>typing.Literal</code> types.
(<code>[#231](python-attrs/cattrs#231) &lt;https://github.com/python-attrs/cattrs/pull/231&gt;</code>_)</li>
<li>Fix unstructuring all tuples - unannotated, variable-length, homogenous and heterogenous - to <code>list</code>.
(<code>[#226](python-attrs/cattrs#226) &lt;https://github.com/python-attrs/cattrs/issues/226&gt;</code>_)</li>
<li>For <code>forbid_extra_keys</code> raise custom <code>ForbiddenExtraKeyError</code> instead of generic <code>Exception</code>.
(<code>[#225](python-attrs/cattrs#225) &lt;https://github.com/python-attrs/cattrs/pull/225&gt;</code>_)</li>
<li>All preconf converters now support <code>loads</code> and <code>dumps</code> directly. See an example <code>here &lt;https://cattrs.readthedocs.io/en/latest/preconf.html&gt;</code>_.</li>
<li>Fix mappings with byte keys for the orjson, bson and tomlkit converters.
(<code>[#241](python-attrs/cattrs#241) &lt;https://github.com/python-attrs/cattrs/issues/241&gt;</code>_)</li>
</ul>
<h2>1.10.0 (2022-01-04)</h2>
<ul>
<li>Add PEP 563 (string annotations) support for dataclasses.
(<code>[#195](python-attrs/cattrs#195) &lt;https://github.com/python-attrs/cattrs/issues/195&gt;</code>_)</li>
<li>Fix handling of dictionaries with string Enum keys for bson, orjson, and tomlkit.</li>
<li>Rename the <code>cattr.gen.make_dict_unstructure_fn.omit_if_default</code> parameter to <code>_cattrs_omit_if_default</code>, for consistency. The <code>omit_if_default</code> parameters to <code>GenConverter</code> and <code>override</code> are unchanged.</li>
<li>Following the changes in <code>attrs</code> 21.3.0, add a <code>cattrs</code> package mirroring the existing <code>cattr</code> package. Both package names may be used as desired, and the <code>cattr</code> package isn't going away.</li>
</ul>
<h2>1.9.0 (2021-12-06)</h2>
<ul>
<li>Python 3.10 support, including support for the new union syntax (<code>A | B</code> vs <code>Union[A, B]</code>).</li>
<li>The <code>GenConverter</code> can now properly structure generic classes with generic collection fields.
(<code>[#149](python-attrs/cattrs#149) &lt;https://github.com/python-attrs/cattrs/issues/149&gt;</code>_)</li>
<li><code>omit=True</code> now also affects generated structuring functions.
(<code>[#166](python-attrs/cattrs#166) &lt;https://github.com/python-attrs/cattrs/issues/166&gt;</code>_)</li>
<li><code>cattr.gen.{make_dict_structure_fn, make_dict_unstructure_fn}</code> now resolve type annotations automatically when PEP 563 is used.
(<code>[#169](python-attrs/cattrs#169) &lt;https://github.com/python-attrs/cattrs/issues/169&gt;</code>_)</li>
<li>Protocols are now unstructured as their runtime types.
(<code>[#177](python-attrs/cattrs#177) &lt;https://github.com/python-attrs/cattrs/pull/177&gt;</code>_)</li>
<li>Fix an issue generating structuring functions with renaming and <code>_cattrs_forbid_extra_keys=True</code>.
(<code>[#190](python-attrs/cattrs#190) &lt;https://github.com/python-attrs/cattrs/issues/190&gt;</code>_)</li>
</ul>
<h2>1.8.0 (2021-08-13)</h2>
<ul>
<li>Fix <code>GenConverter</code> mapping structuring for unannotated dicts on Python 3.8.</li>
</ul>

</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a href="https://github.com/python-attrs/cattrs/commits">compare view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants