Pytest_django model class attribute not filled for __init_subclass__

I have a specific problem.

class A:
    SOME_ATTR: int

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        attr = getattr(cls, "SOME_ATTR", None)
        if not attr:
            raise TypeError(f"Class '{cls.__name__}' must define 'SOME_ATTR'")

classB(models.Model, A):
    SOME_ATTR = 0

In this case everything works as expected but pytest tests. pytest_django creates: cls = <class 'fake.B'> which has no attribute 'SOME_ATTR' Is there any option to enforce it?

I'v tried to add a session scope autouse fixture but it didn't work as it happens before it.

When pytest_django creates fake model classes for testing, it's not properly carrying over class attributes from parent classes, which is causing your __init_subclass__ validation to fail.

Instead of using __init_subclass__ , you could try using a metaclass: It intercept class creation at a more fundamental level than __init_subclass__ so the validation happens during class creation itself, it's harder for testing frameworks to miss it!

class AMeta(type):
    def __new__(mcs, name, bases, attrs):
        cls = super().__new__(mcs, name, bases, attrs)
        if name != 'A':  # Skip validation for the base class itself
            attr = getattr(cls, "SOME_ATTR", None)
            if attr is None:
                raise TypeError(f"Class '{cls.__name__}' must define 'SOME_ATTR'")
        return cls

class A(metaclass=AMeta):
    SOME_ATTR: int

class B(models.Model, A):
    SOME_ATTR = 0

As @sahasrara62 pointed, adding a __new__ method directly to class A is one way to do too.

class A:
    SOME_ATTR: int

    def __new__(cls, *args, **kwargs):
        
        attr = getattr(cls, "SOME_ATTR", None)
        if attr is None and cls.__module__ != '__fake__':  
            raise TypeError(f"Class '{cls.__name__}' must define 'SOME_ATTR'")
        
        
        return super().__new__(cls)

The Reason I'd reccomnd uisng Metaclasses aprroch is it validate during class definition, catching errors immediately when the code is loaded rather than when objects are instantiated. This provides faster feedback during development, so there's no runtime performance penalty. Also Django models already use metaclasses extensively making this conceptually aligned with Django's architecture!

Вернуться на верх