Skip to content

Class decorators are not type checked #3135

Open

Description

@JelleZijlstra
decorator: int = 3

@decorator  # no error
class A:
    pass

class B:
    pass
NewB = decorator(B)  # E: "int" not callable

Mypy doesn't appear to do any checking of class decorators. Applying the decorator manually works.

Activity

  1. JelleZijlstra commented on Apr 5, 2017

    @JelleZijlstra
    MemberAuthor

    Guido and I found out about this in python/typeshed#1136 (comment). Looks like this can be fixed somewhere around analyze_class_decorator in semanal.py.

  2. ilevkivskyi commented on Jun 1, 2017

    @ilevkivskyi
    Member

    A use case reported in #3483

    from typing import Type
    
    class A:
        pass
    
    def deco(cls) -> Type[A]:
        return A
    
    @deco
    class B:
        pass
    
    def func(cls: Type[A]):
        assert cls is A
    
    func(B)

    might be non-trivial to implement. I am adding this example here, so that we will not forget about it.

  3. euresti commented on Feb 3, 2018

    @euresti
    Contributor

    Another possibly easier to solve problem is that the functions called are not type-checked

    from typing import TypeVar, Callable
    _C = TypeVar('_C', bound=type)
    def blah(x:int=17) -> Callable[[_C], _C]: ...
    
    @blah(y=18)
    class A:
        pass
    
    blah(y=18)
    

    Or even more fun

    x=17
    
    @x(huh=17)
    class B:
        pass
    
    x(huh=17)
    
  4. reinhrst commented on May 12, 2018

    @reinhrst

    I'm thinking how this could/should be used in combination with Protocols to deal with class decorators adding some methods to a class

    class FooBar(Protocol):
       def bar(self) -> int:
         return 1
    
    T = TypeVar("T", bound=Type)
    def add_bar(cls: T) -> Intersection[FooBar, T]:
       def bar(self) -> int:
           return 2
      cls.bar = bar
    
    @add_bar
    class Foo: pass
    
    Foo().bar()
    

    Now this depends on Intersection which was described in PEP483, but not implemented; I don't see another way of doing this cleanly right now.

  5. erezsh commented on Jul 15, 2022

    @erezsh

    Are there plans to fix this? It's strange that mypy doesn't support a core syntax.

  6. rileyjohngibbs commented on Nov 11, 2022

    @rileyjohngibbs

    It looks like some of these examples (e.g. the x=17 example above) may have been fixed, so in the interest of having a current, non-working example:

    from typing import Any
    
    def foo(_: Any) -> int:
        return 5
    
    @foo
    class Foo:
        pass
    
    def test_foo() -> None:
        assert Foo + 7 == 12  # Does not pass typechecking

    The error for the final line is: error: Unsupported operand types for + ("Type[Foo]" and "int") [operator].

    Function decorators seem to work fine though:

    def qux(func: Callable[[], str]) -> Callable[[], int]:
        def wrap() -> int:
            return int(func())
        return wrap
    
    @qux
    def five() -> str:
        return "5"
    
    def test_qux() -> None:
        assert five() + 10 == 15  # Passes typechecking
  7. DetachHead commented on Oct 10, 2023

    @DetachHead
    Contributor

    It looks like some of these examples (e.g. the x=17 example above) may have been fixed

    yeah, class decorators are now typechecked, but the issue now is that they are unable to change the type of the class they're decorating. see #11117 (comment)

  8. erezsh commented on Oct 10, 2023

    @erezsh

    yeah, class decorators are now typechecked, but the issue now is that they are unable to change the type of the class they're decorating. see #11117 (comment)

    Imho, this is important functionality.

    For example, it could be useful for implementers of alternative dataclasses, such as pydantic or sqlmodel. Especially once support for intersection gets into mypy. I believe it would be much better than the current approach, that states:

    Mypy does not yet recognize aliases of dataclasses.dataclass, and will probably never recognize dynamically computed decorators.

    (taken from https://mypy.readthedocs.io/en/stable/additional_features.html)

    It would also obsolete the hacky and limited typing_extensions.dataclass_transform.

    Just my two (hopefully correct) cents.

  9. NeilGirdhar commented on Oct 10, 2023

    @NeilGirdhar
    Contributor

    It would also obsolete the hacky and limited typing_extensions.dataclass_transform.

    I don't think it would. The dataclass_transform marks that a class has fields made with field-makers. Whereas an annotated decorator can tell the type checker that a class can be passed to dataclasses functions like fields and replace.

    These are separate concepts. You can have classes with fields that can't be passed to dataclass functions.

  10. CaselIT commented on Jan 27, 2026

    @CaselIT

    This is really an annoying issue. I planned on adding support for typed tables in sqlalchemy sqlalchemy/sqlalchemy#13085

    but this issue makes the decorator form non functional.
    For reference in pyright it works perfectly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions