#35406: Using Django models in function type annotations, without dependency to
settings.configure()
-------------------------------------+-------------------------------------
               Reporter:  HTErik     |          Owner:  nobody
                   Type:  New        |         Status:  new
  feature                            |
              Component:  Database   |        Version:  4.2
  layer (models, ORM)                |
               Severity:  Normal     |       Keywords:  models, typing
           Triage Stage:             |      Has patch:  0
  Unreviewed                         |
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 When using Django defined Models together with type annotation on top
 level functions in a module, the imports to the Django models first
 require calling the global `settings.configure()`.
 When used in a single file this works ok, in larger applications where
 multiple files refer to the same model, this dependency spreads like a
 plague and every file must either itself call `settings.configure()` at
 the very top, or the imports must be placed in very careful order all the
 way from the top level entrypoint of the application, creating huge
 difficulties in reuse, testing, auto sort of imports.
 It also creates implicit dependencies, where some times importing a module
 works, depending on if any of the earlier modules has configured django.

 Best practice for imports is to behave more like declarative code, by not
 having side effects and not cause side effects. Django models and
 `settings.configure()` are the exact opposite of this, creating big
 difficulties with type checkers.

 Consider following basic scenario:

 **models.py**
 {{{
 from django.db import models

 class Dog(models.Model):
     name = models.CharField()

 class Car(models.Model):
     color = models.CharField()
 }}}

 **helpers1.py**
 {{{
 from models import Dog, Car     #
 django.core.exceptions.ImproperlyConfigured: Requested settings, but
 settings are not configured.

 def get_color(foo: Car) -> str:
     return foo.color
 }}}

 How one can solve this is by introducing `settings.configure()` at the
 top, giving us next attempt:

 ------------------------------------------------------------
 **helpers_2.py**
 {{{
 from django.conf import settings
 settings.configure()
 from models import Dog, Car

 def get_color(foo: Car) -> str:
     return foo.color
 }}}

 While this works, it is just pushing the problem forward, now anyone
 importing helpers2.py will have the same problem.

 **application1.py**
 {{{
 from models import Dog           #
 django.core.exceptions.ImproperlyConfigured: Requested settings, but
 settings are not configured.
 from helpers_2 import get_color  # OK

 def main():
     get_color(Car.objects.first())
 }}}


 ------------------------------------------------------------

 What's more problematic about placing `settings.configure()` during
 imports, is that it can not accept input taken when the application
 starts, for example by reading config files or argument parsers.

 **application2.py**
 {{{
 import argparse
 from django.conf import settings
 from models import Dog  # django.core.exceptions.ImproperlyConfigured:
 Requested settings, but settings are not configured.
 from helpers_2 import get_color  # OK


 def main():
     parser = argparse.ArgumentParser()
     hostname = parser.add_argument("--hostname")
     args = parser.parse_args()
     # Configuration based on arguments, configuration files, credential-
 fetchers, and so on.
     settings.configure(args.hostname, etc....)
 }}}

 --------------------------------------

 I have also considered placing `settings.configure()` at the very top
 inside `models.py` itself and accept that any argument-parsers are done in
 this global scope. But this creates difficulties in unit testing, where
 one need to mock the db before even importing a model, which again
 transitively spreads up the chain to any code that imports something that
 imports models.


 ---------------------------
 I'm not sure what the to actually request as a solution for this. Some
 ideas:

 * Provide an alternative lightweight settings.configure() that loads the
 plugins but doesn't connect to the database. This allows partial
 configuration in the top level models, then final configuration when
 application starts.
 * Better integration with typing.TYPE_CHECKING, not sure how, something
 that allows using models in type-annotations without having the complete
 django configured maybe?
 * Example projects using models in type annotations in helper-functions,
 with settings configurable from external sources. To showcase best
 practice of how one should structure such a Django project.
-- 
Ticket URL: <https://code.djangoproject.com/ticket/35406>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-updates+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/0107018f196f1db7-8a25357a-41ef-4cbf-8a9b-fcc550ebddac-000000%40eu-central-1.amazonses.com.

Reply via email to