User vs AbstractBaseUser

Seems like this would be answered somewhere, but I can’t find it. Please feel free to redirect me to another post if it has.

When I access request.user the type is AbstractBaseUser
This mean that calls like user.username cause the typechecker to report an error.

The workaround I have found is something like

user = request.user
assert isinstance(user, User)
username = user.username

Is this best practice, or is there a preferable way?

if just get username, check user is login or not.

username = request.user.username if request.user.is_authenticated else None

Welcome @jonmooser !

What specifically are you looking at that is making you think that request.user is of type AbstractBaseUser? (Are you using a custom user model?)

The code throughout the authentication middleware always assigns an instance of UserModel to user. (See ModelBackend.authenticate)

On my system, print(type(request.user)) gives <class 'django.utils.functional.SimpleLazyObject'> and print(request.user.__class__) gives either <class 'django.contrib.auth.models.AnonymousUser'> or <class 'django.contrib.auth.models.User'>, depending upon whether the user is authenticated or not.

The specific error in VSCode (using pylance for type checking) reads:

Cannot access attribute "username" for class "AbstractBaseUser"
  Attribute "username" is unknown Pylance (reportAttributeAccessIssue)

It looks like it’s coming from the django stubs in pylance, request.pyi:

class HttpRequest:
    GET: QueryDict = ...
    POST: QueryDict = ...
    COOKIES: dict[str, str] = ...
    META: dict[str, Any] = ...

    # ...
    # ...

    # Attributes added by commonly-used middleware
    user: AbstractBaseUser | AnonymousUser
    site: Site
    session: SessionBase

So the runtime returns the correct type, but the language server thinks it’s AbstractBaseUser.
Have others run into this? Do you just ignore it?

I guess it’s not technically a bug because Pylance doesn’t know what specific type of user the authentication middleware will set.

AbstractBaseUser is a base model for creating a user model, and is a model that cannot be used directly.

What is actually used is the User model using AbstractBaseUser as the base.

I have not encountered this. (Nor would I.)

<opinion>
I don’t use any typing in my code. I find it adds more visual clutter to the code than the value it provides. As a result, I don’t use any of the “typing-related” tools in my work environment.
(That Python does not require explicit typing, and provides “duck typing” as a fundamental principle, is one of the reasons I moved to using Python as my primary language of choice 20+ years ago. I have no desire to see Python move anywhere in the direction of Java.)
</opinion>

This is correct - and shows one of the reasons why I’m not a fan of typing in Python.

Technically, not only can the user model be changed (using the AUTH_USER_MODEL setting), but this user model does not need to have any relationship with AbstractBaseUser. Also, if you have multiple authentication backends in your project, request.user can be different types based upon the backend.

Have you looked at using django-stubs? It has a suggestion for how to use typed attributes on HttpRequest

Interesting <opinion>! Thanks for sharing it.

This clearly gets into a much bigger philosophical debate than one little aspect of Django. I think (think) you may be in the minority, and that most python programmers are using pylance or mypy or something for static type checking.

But I could be wrong and your preference of relying on duck typing has certainly given me pause for thought.

All of that said, going back to my original question, would you say my idea of using an assert statement makes sense then?

user = request.user
if user.is_authenticated:
    assert isinstance(user, User)
    ...
    name = user.username

My thinking is that if for some bizarre reason user is not an instance of User, I want it to fail hard and early, before I’ve had a chance to do anything with that object. Thoughts?

I probably am, and that’s ok. I hold many opinions that probably put me in the minority in a number of different areas. Some of that is due to my background, and some due to the nature of most of the work I do. I learned a long time ago that I come to Django with a very different perspective.

Actually, I think I kind of addressed this in my previous response:

The entity, request.user, is whatever your authentication backend returns for the user being authenticated.

Applying that to this case:

The assert enforces a requirement that may not be necessarily correct within the context of this code.

If user is not an instance of User, you do get an error. But what if you want to deploy this in a project using a custom authentication backend that doesn’t use the standard django.auth.User model?

Or, what if it is deployed in a project that uses a custom User model without a username attribute, but names it User? In that case, it will pass the assert, but throw an error at name = user.username anyway.

But that line name = user.username will work if user is something like MyCustomUser, if that class provides a username attribute or property.

So this brings me back around to a more basic question.

Do you really care whether request.user is an instance of a class named User? If so, why? (And if so, I would suggest that you’re better off making this test in middleware rather than scattered around multiple views.)

Or is your concern only that request.user provides the attributes and properties that the rest of this view requires?

Whether or not this question makes a difference depends upon the scope of usage of your code.

If this code is specific to one particular project in a given environment, then it probably doesn’t matter.

But if you’re building a library of code - even if it’s for your own personal use and not going to be shared outside your organization - then these are types of questions you would want to consider.