Skip to content

ModelBackend.auhenticate signature should be more relaxed #2591

Open
@PedroPerpetua

Description

@PedroPerpetua

Enhancement

What's wrong

The current type stubs define the authenticate method of the ModelBackend (django.contrib.auth.backends.ModelBackend) as such:

def authenticate(
    self, 
    request: HttpRequest | None, 
    username: str | None = ..., 
    password: str | None = ..., 
    **kwargs: Any,
) -> _UserModel | None: ...

The parent class, BaseBackend defines it as such:

def authenticate(self, request: HttpRequest | None, **kwargs: Any) -> _UserModel | None: ...

When Django looks for which authentication backend to use, it'll always call said backend using the parameters as kwargs, until it finds the first backend that accepts the parameters and returns a valid user (or None if they all fail). The documentation itself instructs the user to define it's own kwargs when defining a custom authentication backend (relevant docs).

However, a very common use case here is to subclass ModelBackend instead of BaseBackend, in cases where we want to make simple adjustments to how authentication is done, but not how it's handled once you have the specific user; in this cases, you'll subclass the ModelBackend, which already has all the code to handle permissions that BaseBackend does not, and simply overwrite the authenticate method so that instead of, for example, a username and password, it uses a token or a code or something of the sorts. For example:

class MyCustomBackend(ModelBackend):

    def authenticate(self, code: Optional[str] = None, **kwargs: Any) -> Optional[User]:
        return User.get_user_for_code(code)

In this simple example Mypy will give an "override" error: Signature of "authenticate" incompatible with supertype "ModelBackend", even though this backend does not use the username / password at all.

Alternatives

There are already alternatives to getting around this of course; simply #type: ignore[override] will do, or adding the typed arguments to the definition even though they are not used at all, as such:

class MyCustomBackend(ModelBackend):

    def authenticate(
        self, 
        username: Optional[str] = None, 
        password: Optional[str] = None, 
        code: Optional[str] = None, 
        **kwargs: Any
    ) -> Optional[User]:
        return User.get_user_for_code(code)

How is that should be

The stubs for the ModelBackend should not override the parent class' method definition at all (it should not define it at all and simply inherit). This will be inline to how all authentication backends are used, and allow users to override with their own type definitions without extra work.

System information

  • OS: -
  • python version: 3.13.2
  • django version: 5.1.7
  • mypy version: 1.15.0
  • django-stubs version: 5.1.3
  • django-stubs-ext version: 5.1.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions