Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Getting the next structure hook

Open
#794 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
25/100
Issue type
Feature
Clarity
Needs clarification
Activity status
Active
Tech stack
python
Domain
data

Research direction

The issue asks how to chain a pre-normalization step in front of structure hooks, and it mentions get_structure_hook, make_dict_structure_fn, register_structure_hook_factory and use_class_methods. Read how those hooks are resolved and why a generic like list[Foo] raises StructureHandlerNotFoundError, and check the linked attrs issue for the attrib.type string behaviour. Done would be a maintainer-agreed design for field converters running before structuring, with tests for the ensure_list case.

Written by the indexing model from the issue text.

Description

I am trying to wrap and combine attrs.Converter and the structure hook to have a pre-normalization step before the structure logic kicks in. There are 2 issues I hit

Pre-normalizing the class

Here the issue is that I cannot get the original (or next preferred) structure, roughly can simulate it with

use_class_methods(raw_converter, "from_raw", "to_raw")

@define
class Foo:
    a: str
    b: int = 1

    @classmethod
    def from_raw(cls, data, converter):
        if isinstance(data, str):
            data = {"a": data}
        if not isinstance(data, dict):
            raise TypeEroor("Unsupported")
        original_structure = structure.get_structure_hook(cls)
        return original_structure(data, cls)

The recursion here is rather obvious, but there is no way to get n layers further down in the preference to get the next structure hook. Even if it was to be hard-coded or only go one layer further would probably work for most cases.

Pre-normalizing the fields

This is most likely a PEBKAC, but I get StructureHandlerNotFoundError when trying to get the structure hook of a generic. Basically the end goal is to have

@define
class Foo:
    a: str

def ensure_list(data):
    if not isinstance(data, list):
        data = [data]
    return data

@define
class Bar:
    foo: list[Foo] = field(converter=ensure_list)

@raw_converter.register_structure_hook_factory(has)
def structure_attrs_with_converter(cls, converter):
    cls_structure = cattrs.gen.make_dict_structure_fn(
        cls,
        converter,
        **{
            attrib.name: override(struct_hook=attr_structure)
            for attrib in attrs.fields(cls)
            if (
                attr_structure := structure_field_with_converter(
                    attrib=attrib,
                    converter=converter,
                )
            )
        },
    )
    return cls_structure

def structure_field_with_converter(attrib, converter):
    if not attrib.converter:
        return None
    original_structure = converter.get_structure_hook(attrib.type)

    def structure(raw_value, _type):
        value = attrib.converter(raw_value, None, attrib)
        return original_structure(value, _type)

    return structure

core of the issue here is that attrib.type given here is a str, not even a ForwardRef that I could later evaluate 🤔
Edit: Aha, it's an attrs bug https://github.com/python-attrs/attrs/issues/1596

Also I have tried to get attrs.Converter and cattrs to work nicely together, but so far this is the only way I found feasible. The issue is that the attrs.Converter must run before the main structure of the class, otherwise it will try to structure each element in the foo key before making sure it is a list in this example. If there are other ways to design this, that would be welcome, since this can get a bit messy.


This might be related to https://github.com/python-attrs/cattrs/issues/659?

Dominant language
Python
Stars
1.1k
Forks
171
Avg merge
14h 46m
Merged PRs (30d)
5

Getting set up

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.

More from python-attrs/cattrs

All issues in python-attrs/cattrs

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.