Skip to content

Allow to register new class/dataclass/attribute makers in attrs plugin #5406

Open

Description

@hynek

This is a feature request.

attrs is very much intended to be composable (e.g. https://github.com/hynek/environ_config) but currently the attrs plugin only works on the core library's functions:

# The names of the different functions that create classes or arguments.
attr_class_makers = {
'attr.s',
'attr.attrs',
'attr.attributes',
}
attr_dataclass_makers = {
'attr.dataclass',
}
attr_attrib_makers = {
'attr.ib',
'attr.attrib',
'attr.attr',
}

I would like to be able to signal somehow to mypy, that some function is a wrapper around @attr.s/@attr.ib/@attr.dataclass. Not only would third-party applications benefit, but I could really use it while experimenting on python-attrs/attrs#408.

Any ideas/plans how we could achieve that?

cc also @euresti & @chadrik

Activity

  1. euresti commented on Jul 31, 2018

    @euresti
    Contributor

    FYI in our code I literally append to these lists in a mypy plugin.

    I did have a vision that the plugin could support an @class_maker(...) decorator that would do nothing at runtime but let the plugin know that this method (along with some default values) is a class_maker or attrib_maker.

    So you'd do:

    @class_maker(auto_attribs_default=True)
    def my_class_maker(...):
       ...
    

    Or something like that.

  2. ilevkivskyi commented on Aug 1, 2018

    @ilevkivskyi
    Member

    Another possible pattern we can support is:

    if TYPE_CHECKING:
        custom_maker = attr.s
    else:
        def custom_maker(cls):
            ...
  3. ilevkivskyi commented on Jan 22, 2019

    @ilevkivskyi
    Member

    The same problem exists for dataclasses, see #6239

  4. ilevkivskyi commented on May 8, 2019

    @ilevkivskyi
    Member

    Just FTR cross-posting the workaround that currently works:

    from typing import TYPE_CHECKING
    if TYPE_CHECKING:
        from attr import s as custom_maker
    else:
        def custom_maker(cls):
            ...

    but it is arguably worse than #5406 (comment), that currently doesn't work.

  5. euresti commented on May 8, 2019

    @euresti
    Contributor

    Heh. Here's what I did in our code base. I added a plugin that adds some methods when imported. (Did y'all wonder why I made those variables globals?)

    from mypy.plugin import Plugin
    from mypy.plugins.attrs import (
        attr_attrib_makers,
        attr_class_makers,
        attr_dataclass_makers,
    )
    
    # These work just like attr.dataclass
    attr_dataclass_makers.add("my_module.method_looks_like_attr_dataclass")
    
    # This work just like attr.s
    attr_class_makers.add("my_module.method_looks_like_attr_s")
    
    # These are our attr.ib makers.
    attr_attrib_makers.add("my_module.method_looks_like_attrib")
    
    class MyPlugin(Plugin):
        # Our plugin does nothing but it has to exist so this file gets loaded.
        pass
    
    
    def plugin(version):
        return MyPlugin
    
  6. LecrisUT commented on Apr 9, 2026

    @LecrisUT

    A checkup since it has been quite a long time. Are there any updates or cleaner workarounds?

    I found from the mentions a cleaner one

    if TYPE_CHECKING:
        from attrs import define
    else:
        def define(cls):
            cls.__init_subclass__ = UnsupportedSubclassing.complain
            return attrs.define(cls)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions