3

I have added django-model-utils to an existing (large) project, and the build is now failing, as part of the build includes static type checking with mypy.

It complains that models that I have added objects = InheritanceManager() to, don't have attributes for reverse ForeignKeys, if the reverse FK is accessed in a method on that model. For example, take the following:

class Student(Model):
    school = ForeignKey(School, related_name='students')

class School(Model):
    objects = InheritanceManager()  # IRL School is a subclass of some other model
    def something(self):
        return self.students.filter(...)

Then running mypy on it, will return:

error: "School" has no attribute "students"

And even if I remove the related_name, and use self.student_set (i.e. the default django relation), it will still produce the same type of error. Only removing the InheritanceManager fixes the static type checking. Of course, I'm using it for a reason as I need to select_subclasses elsewhere in the code.

Has anyone come across this, or have a fix?

David Downes
  • 1,145
  • 10
  • 24

1 Answers1

0

django-stubs uses plugin to add all managers. This plugin is triggered only if added manager is a "subclass" (not just real subclass, but also recognizable by mypy as such) of models.Manager.

django-model-utils is untyped, so InheritanceManager is in fact Any for mypy, and plugin does not see it. To solve exactly this issue I was adding py.typed marker to django-model-utils as a CI stage after package installation. You can also use a fork with py.typed or create a stub package for django-model-utils. This can result in other issues and doesn't give good type checking (all unannotated methods have Any as implicit arguments and return type), but is better than nothing. For my needs the marker was sufficient.

py.typed marker is an empty file located in package root (venv/lib/.../django_model_utils/py.typed) - it tells mypy that package does not need separate stubs and contains all necessary types.

STerliakov
  • 4,983
  • 3
  • 15
  • 37
  • But I'm not getting errors about stubs or any problems directly with model utils as a package (like you would get if you don't add django-stubs to a django project), it's literally just these reverse foreign keys that are the issue. Would a py.typed marker help with that somehow? – David Downes Jul 29 '22 at 12:01
  • Yes, it would. You don't have errors from this packages, most probably because you have `ignore_missing_imports=True` or `follow_imports=skip` `mypy` config, so everything imported from `django_model_utils` is `Any` and does not have `models.Manager` as base. I contributed to `django-stubs` plugin system, so I'm 100% sure that `objects = Something()` adds reverse managers iff `Something <: models.Manager`. – STerliakov Jul 30 '22 at 19:07
  • @DavidDownes have you found a reasonable solution to this problem? We're facing the exact same issue. – Barnercart Feb 27 '23 at 09:07
  • @Barnercart to resolve just the issue in the question my answer is sufficient - just checked on a demo project setup. `touch venv/lib/python3.10/site-packages/model_utils/py.typed` creates a marker for `mypy`, you can run this in CI pipeline. Also you can fork `django-model-utils` and add py.typed. If you need more support than just understanding their manages as `models.Manager` suclasses, you may want to run `stubgen` and create a stub package (and release it! The community will like this), read [here](https://mypy.readthedocs.io/en/stable/stubgen.html). – STerliakov Feb 27 '23 at 11:13
  • @SUTerliakov thanks but maybe it is reasonable to just wait a support on this matter from `django-model-utils` themselves? – Barnercart Feb 27 '23 at 12:14
  • This may surely happen at some point, but I doubt it'll happen any soon. Django is a very bad candidate for typing, and this will be not widely useful (and, of course, this is about maintainer's time - I suppose there are many more interesting things to do). You may rise an issue on their tracker to ask the maintainer directly, but I think you have a better chance to DIY quickly instead of asking somebody else to do it in their free time. – STerliakov Feb 27 '23 at 13:22