activist
activist copied to clipboard
Investigate and potentially adding mypy django-stubs plugin
Terms
- [x] I have searched open and closed issues
- [x] I agree to follow activist's Code of Conduct
Issue
This issue would look into the benefits of adding in typeddjango/django-stubs to the mypy plugins used in activist. Once a report on the overall positives of adding it in has been finished, a decision would be made on whether it should be added. From there a PR could be raised to add the plugin and fix all the errors and warnings that it brings to our attention :)
Hi! I'd be happy to help with this issue.
Thanks for your willingness to help, @viditi08! Please let us know if you need some support. It'd be great to get the basic changes and benefits proposed in the issue after the investigation, and then we can move towards a PR :)
Assigning now 😊
Thanks for assigning it to me! I’ll investigate the issue and propose the necessary changes and benefits. I'll keep you updated, and we can move forward with the PR once everything's ready! 😊
Sounds wonderful, @viditi08! 😊
django-stubs Investigation Report Current Status
django-stubs is in requirements-dev.txt mypy_django_plugin.main is configured in pyproject.toml However, imports are skipped in Django apps, preventing full type checking
Benefits
Stronger type checking for Django models, querysets, and ORM Early detection of Django-specific errors Better IDE support with accurate auto-completion Improved documentation through precise type annotations Safer refactoring of Django code
What I Will Do
Remove follow_imports = "skip" for Django apps in pyproject.toml Enable django-stubs type checking for the authentication app first Fix initial type errors that arise from the stricter checking Document the most common typing patterns for the project Propose updates to the CI workflow to include mypy checks
This implementation will improve code quality and developer experience while maintaining a gradual approach to avoid disrupting current development.
All sounds good, @viditi08 :) Feel free to start working on the first PR 😊
Hey , When i comment out follow_imports = "skip" , I am getting type erroes for factory , Do you want me to create custom stub file with minimal type annotations to fix those type errors ? or just simply ignore the errors ?
For now let's ignore them, but let's keep this in mind during the review :) Thanks for checking!
Thanks for the feedback! I've raised a PR request.
Perfect! Looking forward to the review :) :)
Sorry for the wait on bringing in the PR, @viditi08 :) What are you thinking for the next steps? Do you want to raise PRs on a file by file basis and then we can change ignore_errors to false once that's all done?
Yes, I can work on a file-by-file basis and remove those errors.
Fantastic, @viditi08 :) Great to have your support here!
Hello! I would love to contribute to this issue if there’s an opportunity. What steps do I need to take to get started?
This is a great issue to split up the work on, @qabilityp :) Here's a list of the issues we're dealing with, which can be seen by setting ignore_errors = false in the [[tool.mypy.overrides]] section of backend/pyproject.toml and then running mypy ./backend --config-file ./backend/pyproject.toml:
backend/events/factories.py:9: error: Skipping analyzing "factory": module is installed, but missing library stubs or py.typed marker [import-untyped]
backend/events/factories.py:24: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/events/factories.py:65: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/events/factories.py:87: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/events/factories.py:110: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/events/factories.py:124: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/events/factories.py:135: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/events/factories.py:154: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/authentication/factories.py:8: error: Skipping analyzing "factory": module is installed, but missing library stubs or py.typed marker [import-untyped]
backend/authentication/factories.py:15: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/authentication/factories.py:26: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/authentication/factories.py:52: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/authentication/factories.py:78: error: Untyped decorator makes function "verification_partner" untyped [misc]
backend/communities/organizations/factories.py:9: error: Skipping analyzing "factory": module is installed, but missing library stubs or py.typed marker [import-untyped]
backend/communities/organizations/factories.py:25: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/communities/organizations/factories.py:52: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/communities/organizations/factories.py:63: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/communities/organizations/factories.py:83: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/communities/organizations/factories.py:95: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/communities/organizations/factories.py:110: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/communities/organizations/factories.py:129: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/communities/organizations/factories.py:142: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/communities/groups/factories.py:9: error: Skipping analyzing "factory": module is installed, but missing library stubs or py.typed marker [import-untyped]
backend/communities/groups/factories.py:9: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
backend/communities/groups/factories.py:22: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/communities/groups/factories.py:45: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/communities/groups/factories.py:57: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/communities/groups/factories.py:70: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/communities/groups/factories.py:89: error: Class cannot subclass "DjangoModelFactory" (has type "Any") [misc]
backend/events/serializers.py:127: error: Unsupported operand types for > ("datetime" and "None") [operator]
backend/events/serializers.py:127: error: Unsupported operand types for < ("datetime" and "None") [operator]
backend/events/serializers.py:127: note: Both left and right operands are unions
backend/events/views.py:41: error: Function is missing a return type annotation [no-untyped-def]
backend/events/views.py:43: error: Incompatible types in assignment (expression has type "tuple[type[IsAuthenticated]]", variable has type "tuple[type[IsAuthenticatedOrReadOnly]]") [assignment]
backend/events/views.py:50: error: Return type "EventSerializer | EventPOSTSerializer" of "get_serializer_class" incompatible with return type "type[BaseSerializer[Event]]" in supertype "GenericAPIView" [override]
backend/events/views.py:52: error: Incompatible return value type (got "type[EventPOSTSerializer]", expected "EventSerializer | EventPOSTSerializer") [return-value]
backend/events/views.py:54: error: Incompatible return value type (got "type[EventSerializer]", expected "EventSerializer | EventPOSTSerializer") [return-value]
backend/events/views.py:76: error: "EventSerializer" not callable [operator]
backend/events/views.py:76: error: "EventPOSTSerializer" not callable [operator]
backend/communities/groups/views.py:41: error: Function is missing a return type annotation [no-untyped-def]
backend/communities/groups/views.py:42: error: Unsupported operand types for in ("str | None" and "str") [operator]
backend/communities/groups/views.py:43: error: Incompatible types in assignment (expression has type "tuple[type[IsAuthenticated]]", variable has type "tuple[type[IsAuthenticatedOrReadOnly]]") [assignment]
backend/communities/groups/views.py:52: error: Incompatible return value type (got "type[GroupPOSTSerializer]", expected "GroupSerializer | GroupPOSTSerializer") [return-value]
backend/communities/groups/views.py:54: error: Incompatible return value type (got "type[GroupSerializer]", expected "GroupSerializer | GroupPOSTSerializer") [return-value]
backend/communities/groups/views.py:79: error: "GroupSerializer" not callable [operator]
backend/communities/groups/views.py:79: error: "GroupPOSTSerializer" not callable [operator]
backend/communities/organizations/views.py:14: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined]
Found 45 errors in 8 files (checked 60 source files)
Here's a breakdown of the 45 errors in 8 files:
backend/events/factories.py: 8 errorsbackend/authentication/factories.py: 5 errorsbackend/communities/organizations/factories.py: 9 errorsbackend/communities/groups/factories.py: 7 errorsbackend/events/serializers.py: 3 errorsbackend/events/views.py: 7 errorsbackend/communities/groups/views.py: 7 errorsbackend/communities/organizations/views.py: 1 error
Maybe you could send along a PR for the last four files, so the views files and the serializer file, @qabilityp? @viditi08, would you want to send along one or multiple PRs for the factories files? 😊
Assigning @qabilityp for the help here :)
Sounds great! Thanks for the breakdown 🙌
I'll go ahead and take care of the factory-related files and start working through the mypy issues in those modules. I'll plan to send one or multiple PRs depending on how the changes shape up.
Sounds perfect, @viditi08 :) :)
Once #1239 is merged these are the errors that we'll have left:
backend/content/serializers.py:166: error: Return type "list[Image]" of "create" incompatible with return type "Image" in supertype "ModelSerializer" [override]
backend/content/serializers.py:166: error: Return type "list[Image]" of "create" incompatible with return type "Image" in supertype "BaseSerializer" [override]
backend/events/serializers.py:137: error: Unsupported operand types for > ("datetime" and "int") [operator]
backend/events/serializers.py:137: error: Unsupported operand types for < ("datetime" and "None") [operator]
backend/events/serializers.py:137: error: Unsupported operand types for < ("int" and "None") [operator]
backend/events/serializers.py:137: error: Unsupported operand types for > ("int" and "datetime") [operator]
backend/events/serializers.py:137: note: Both left and right operands are unions
backend/events/serializers.py:159: error: Unsupported operand types for > ("datetime" and "int") [operator]
backend/events/serializers.py:159: error: Unsupported operand types for < ("datetime" and "None") [operator]
backend/events/serializers.py:159: error: Unsupported operand types for < ("int" and "None") [operator]
backend/events/serializers.py:159: error: Unsupported operand types for > ("int" and "datetime") [operator]
backend/events/serializers.py:159: note: Both left and right operands are unions
@viditi08, we made the decision to just ignore the factories for mypy as they weren't errors we could fix. Would one of you have interest in opening a PR for the rest of the errors above? We'll need to check the types for the event serializers as well as the return type in the content one :)
Contribution to mypy + django-stubs integration
Hi, I’m Heric — this is my first contribution to the activist project. I’m a backend developer with a background in data, journalism, and open tools. I’m currently exploring issues where I can help improve the robustness and type safety of the codebase.
Below is my detailed contribution regarding issue #1199, which I’d be happy to formalize in a PR.
As part of the initiative to progressively enforce static type checking in the codebase, I’ve forked the repository and worked locally on a large subset of the mypy errors raised when disabling ignore_errors = true in the pyproject.toml overrides for the following modules:
[[tool.mypy.overrides]]
module = ["authentication.*", "content.*", "communities.*", "events.*"]
# ignore_errors = true <-- temporarily disabled during my contribution
Parfait, voici une version entièrement revue et restructurée de la section What I’ve done, avec un lien explicite entre chaque type d’erreur, sa solution, et les fichiers concernés.
What I’ve done
After temporarily disabling ignore_errors = true in pyproject.toml, I ran mypy with django-stubs activated on the main Django modules of the project.
This surfaced 34 type errors across 10 files (models, views, and serializers), which I addressed systematically.
Below is a breakdown of the error types, the fixes applied, and the corresponding files:
1. Typing issues on Django ORM fields (var-annotated)
Error:
Need type annotation for "flags"
Cause:
mypy cannot infer the type of Django ManyToManyField, even with django-stubs.
Fix:
Explicitly annotate with Any:
from typing import Any
flags: Any = models.ManyToManyField(...)
Files fixed:
content/models.pyevents/models.pycommunities/organizations/models.pycommunities/groups/models.py
2. Missing return type annotations in API views (no-untyped-def)
Error:
Function is missing a return type annotation
Fix:
Manually added -> Response for each method lacking it.
Files fixed:
authentication/views.pycommunities/groups/views.py(GroupAPIView,GroupFlagViewSet)communities/organizations/views.py(OrganizationFaqViewSet)
3. Incompatible assignment of permission classes (assignment)
Error:
Incompatible types in assignment (expression has type ..., variable has type ...)
Cause:
Inside get_permissions(), reassigning self.permission_classes dynamically with another tuple (IsAuthenticated) caused a conflict with the static class declaration.
Fix: Replaced with direct return of permission instances:
def get_permissions(self) -> List[BasePermission]:
if self.request.method in SAFE_METHODS:
return [IsAuthenticatedOrReadOnly()]
return [IsAuthenticated()]
File fixed:
communities/groups/views.py(GroupAPIView)
4. Wrong return type in get_serializer_class() (return-value)
Error:
Incompatible return value type
Cause: The method returned a class instead of an instance (or not matching the expected union).
Fix: Used precise union types in the return annotation:
def get_serializer_class(self) -> type[GroupSerializer] | type[GroupPOSTSerializer]:
File fixed:
communities/groups/views.py(GroupAPIView)
5. Serializers used as non-callables (operator)
Error:
"GroupSerializer" not callable
Cause: Mypy mistook the serializer type as non-callable due to insufficient type hints.
Fix: Disambiguated the type before calling:
serializer_class = self.get_serializer_class()
serializer = serializer_class(data=request.data)
File fixed:
communities/groups/views.py(GroupAPIView)
6. Overridden method with incompatible return (override)
Error:
Return type "list[Image]" incompatible with supertype "ModelSerializer"
Cause:
ImageSerializer.create() returns a list instead of a single instance.
Fix:
Relaxed the return type to Any:
def create(...) -> Any:
File fixed:
content/serializers.py
7. Unsafe comparisons between types (operator)
Error:
Unsupported operand types for > or <
Cause:
In EventSerializer.validate(), datetime, int, and None were compared without proper checks.
Fix:
Used isinstance(..., datetime) before comparisons.
File fixed:
events/serializers.py
8. Incompatible id lookup (misc)
Error:
Incompatible type for lookup 'id': (got "Any | None", expected "UUID | str")
Cause:
Using filter(id=data.get("id")) without ensuring the value was not None.
Fix:
Used cast(UUID | str, ...):
from typing import cast
faq_id = cast(UUID | str, data.get("id"))
Files fixed:
events/views.py(EventFaqViewSet)communities/groups/views.py(GroupFaqViewSet)communities/organizations/views.py(OrganizationFaqViewSet)
9. Undeclared export in drf-spectacular (attr-defined)
Error:
Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes"
Cause:
OpenApiTypes is not declared in the type stubs.
Fix: Isolated the import and added a type ignore:
from drf_spectacular.utils import OpenApiTypes # type: ignore[attr-defined]
File fixed:
authentication/views.py
Approach
I’ve adopted a progressive, non-breaking refactoring approach:
- No business logic has been changed.
- All fixes stay type-safe and compatible with Django's ORM behavior.
- Temporary castings (
cast(UUID | str, ...)) have been added only when the source type couldn't be refined otherwise. - No
# type: ignorehas been used without justification or fallback.
Tests
- All changes pass
mypywithignore_errors = false. - Functional behavior has been preserved and scoped to well-understood views (e.g.
UserFlagViewSet,GroupAPIView,OrganizationFaqViewSet, etc.).
Next step
I would now be happy to open a pull request from my fork, which cleans up a large portion of the typing-related issues across the backend.
Let me know if you'd prefer a segmented PR per module (e.g. authentication, events, etc.) or a unified PR referencing this issue.
Thanks for your guidance and for the awesome work on this project — Heric
Hi @hericlibong 👋 You'd be welcome to open a PR with ignore_errors = false and the changes that you've made :) We'll take a look from there.
Closed by #1332 🚀 Thanks for all the hard work here, @hericlibong and @viditi08! Please let us know if there are other issues you would have interest in working on 😊