pythonfactorymypylinter

How to type python class factory with linter?


I have this code

from abc import ABC
from typing import Type


class AbstractFoo(ABC):
    """Abstract foo."""
    foo_attr: int


def foo_factory(foo_attr_val: int) -> Type[AbstractFoo]:

    class Foo(AbstractFoo):
        foo_attr = foo_attr_val

    return Foo


FooA = foo_factory(5)


class FooB(FooA):
    """Public concrete foo."""

print(FooB.foo_attr)

This executes just fine, but then I run mypy with mypy foo.py, I get an error that looks like

foo.py:21: error: Variable "foo.FooA" is not valid as a type
foo.py:21: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
foo.py:21: error: Invalid base class "FooA"
Found 2 errors in 1 file (checked 1 source file)

I don't understand what is wrong here and why this type is invalid. What's the way to fix this?

I'm currently on Python 3.9 with mypy version 0.971.


Solution

  • Your problem is that mypy does not support dynamic base classes (doesn't matter if they are well-defined). See this mypy issue for more workarounds and details.

    If you're happy enough to have a factory producing class without major modifications, you can just pretend that return type is that class (try me online!):

    from abc import ABC
    from typing import Type
    
    
    class AbstractFoo(ABC):
        """Abstract foo."""
        foo_attr: int
    
    
    def foo_factory(foo_attr_val: int) -> Type[AbstractFoo]:
    
        class Foo(AbstractFoo):
            foo_attr = foo_attr_val
    
        return Foo
    
    
    if TYPE_CHECKING:
        FooA = AbstractFoo
    else:
        FooA = foo_factory(5)
    
    
    class FooB(FooA):
        """Public concrete foo."""
    
    print(FooB.foo_attr)
    

    TYPE_CHECKING is a special constant that is False at runtime and True for type checkers. Here you make type checker think that your FooA is just a type alias (and on py3.10+ or with typing_extensions you can even make it explicit with TypeAlias type annotation).