2025, Sep 30 17:00
How to resolve PyRight reportOptionalMemberAccess on Django ModelForm.clean and cleaned_data
Why PyRight flags cleaned_data.get in Django ModelForm.clean (reportOptionalMemberAccess): django-stubs Optional return and how to fix by an assert.
PyRight on Django projects sometimes flags a call to cleaned_data.get inside a ModelForm.clean implementation with reportOptionalMemberAccess. The code runs fine, autocomplete for ORM models works, yet this one line triggers a warning. The reason is not in your editor; it’s in the types that power the checker.
Problem example
The pattern looks familiar: get the dict returned by super().clean() and read a value from it.
from django.forms import ModelForm
class TicketForm(ModelForm):
    def clean(self):
        normalized_map = super().clean()
        kickoff_date = normalized_map.get("start_date")
PyRight reports: "get" is not a known attribute of "None" (reportOptionalMemberAccess).
Why this happens
The types provided by django-stubs explicitly model clean as possibly returning None:
class BaseForm(RenderableFormMixin): # ... def clean(self) -> dict[str, Any] | None: ...
That optional return type exists because form subclasses are allowed to skip returning the dictionary. As discussed in the issue tracker, “Form subclasses are allowed to skip returning the dictionary, so the type in form needs to allow none.” A FormSet is an example where this behavior appears. Given that contract, PyRight infers that the result of super().clean() might be None, and calling .get on it is unsafe without a type guard.
It’s also possible to see different behavior between the PyRight CLI and what you observe in VS Code, but regardless of the UI, the underlying type information from django-stubs is what drives the warning here.
Fix: make the Optional explicit and narrow the type
The quickest way to satisfy the checker is to narrow the Optional at the point of use. An assertion communicates to both the reader and the type checker that the value is not None when accessed.
from django.forms import ModelForm
class TicketForm(ModelForm):
    def clean(self):
        normalized_map = super().clean()
        assert normalized_map is not None
        kickoff_date = normalized_map.get("start_date")
This change aligns your code with the annotated API: clean may return a dict or None, and your implementation proves to the checker that you only use it as a dict after ensuring it’s not None.
Why this detail matters
Static analysis only works as well as the contracts it enforces. Here, django-stubs encodes the real-world behavior that clean is allowed to return None. PyRight surfaces that possibility so you don’t accidentally dereference None. Making the possibility explicit results in clearer, safer form code and avoids inconsistent type assumptions between tools.
Takeaways
If PyRight warns about reportOptionalMemberAccess on cleaned_data, it’s honoring the type contract from django-stubs. Treat the value as Optional and narrow it before calling .get. This simple guard keeps your forms both correct with respect to the stubs and robust at runtime.
The article is based on a question from StackOverflow by guettli and an answer by willeM_ Van Onsem.