Problems with creating a type protocol for Django user class

I have a function that operates on users, but in one of the projects that uses it we don't use Django user. We have a dataclass that pretends to be a user.

Problems started when I was trying to type this function. I couldn't use AbstractBaseUser as the user type, because that would obviously not work in this project with dataclass user.

However, Python has a solution for such problems - protocols!

I quickly created a user protocol containing all features I used in the function:

class ManagerProtocol(Protocol):
    def get(self, **kwargs: Any) -> UserProtocol:
        ...


class UserProtocol(Protocol):
    objects: ManagerProtocol

    class DoesNotExist(Exception):
        ...

and adjusted our dataclass user to follow this protocol:

class FakeManager:
    def get(self, **kwargs: Any) -> StaticUser:
        return StaticUser(user_id=kwargs.get('user_id', ''))


@dataclass(frozen=True)
class StaticUser:
    user_id: str
    is_authenticated: bool = True
    objects = FakeManager()

    class DoesNotExist(Exception):
        pass

and then I ran mypy and got this:

error: Argument 1 to "function" has incompatible type "StaticUser"; expected "UserProtocol"  [arg-type]
note: Following member(s) of "StaticUser" have conflicts:
note:     DoesNotExist: expected "Type[UserProtocol.DoesNotExist]", got "Type[StaticUser.DoesNotExist]"
note:     objects: expected "ManagerProtocol", got "FakeManager"

I've read some articles on the web and the solution was clear - use properties instead of class members!

class UserProtocol(Protocol):
    @property
    def objects(self) -> ManagerProtocol:
        ...

    @property
    def DoesNotExist(self) -> Type[Exception]:
        ...

but after running mypy again another problems emerged:

error: "Callable[[UserProtocol], ManagerProtocol]" has no attribute "get"  [attr-defined]
error: Exception type must be derived from BaseException  [misc]

The protocol compatibility was ok, but mypy saw errors in places where I actually used the UserProtocol class.

Do you have any idea how to create a protocol that would work here without causing type errors?

Back to Top