Skip to content

Making third-party extensions a bit easier #1543

@LecrisUT

Description

@LecrisUT

I am researching how we can expand the field with more metadata on our project. The documentation was very helpful for getting started, but I've hit a few places where there is a bit of friction

Adding field decorators requires extending attr._make._CountingAttr

This is way more complicated then it seems at first glance because:

  • the _CountingAttr keeps an internal counter for the ordering
  • changing the return type of field makes it be picked up as a custom default instead of an extension of _CountingAttr

The initial design I considered was

def field(*, metadata=None, **kwargs: Any) -> Any:
    if not metadata:
        metadata = {}
    metadata.setdefault("my_ext", MyMetadata())
    return MyCountingAttr(attrs.field(**kwargs, metadata=metadata))

class MyCountingAttr(attr._make._CountingAttr):
    def __init__(self, ca):
        for f in ca.__slots__:
            setattr(self, f, getattr(ca, f))
        attr._make._CountingAttr.cls_counter -= 1
        self.counter -= 1

    def new_decorator(self, meth):
        my_metadata: TmtAttrsMetadata = self.metadata["my_ext"]
        my_metadata.func = meth
        return meth

@attrs.define
class MyMetadata:
    func: Callable[..., Any] | None = None

@attrs.define
class Example:
    x: field()

    @x.new_decorator
    def _x_my_func(self):
        pass

but this fails when it gets to

attrs/src/attr/_make.py

Lines 403 to 407 in f0e420b

ca_names = {
name
for name, attr in cd.items()
if attr.__class__ is _CountingAttr
}

Could not find a way around it besides changing that to a issubclass check

Many useful internal functions are not exposed

For example attr._make._determine_whether_to_implement would be really nice to not have to re-implement, especially when it comes to the correct handling of classmethod. Admittedly my usage there is quite weird (injecting some methods/classmethods based on inputs to the @define decorator) and it could be better handled with inheritance/metaclass, but it's the first example of the internal helpers that I've found in my code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions