Importing settings files directly is more common than you think.
Tags: django, webdev, pyhton, programming
Published: 2021-01-08 09:15:36

Django Doctor audits code and auto fixes Django anti-patterns. We checked 666 Django projects for problems hindering maintainability and found that **5% of the Django websites don't use `django.conf.settings`** [read more](https://django.doctor/advice/C4006).

We also manually checked a sample of the affected lines in the public repos, and were shocked that this was not "beginners making mistakes" - most were in mature large projects. That makes sense: the longer a codebase lives the greater the scope for tech debt to creep in, and the more lines of code the easier it is for the bad to hide in plain sight. Indeed I suspect, like most people, you don't read every single import when you open a python file. It's easy to miss this tech debt.

_How would you solve maintainability problems? Try [our Django refactor challenge](https://django.doctor/challenge/models)._

## Naked settings, if you will

Django best practice tells us importing `settings.py` is bad and we should instead use `from django.conf import settings`, hence Django Doctor gives this advice when reviewing GitHub PRs:

![Code review by django.doctor ](https://dev-to-uploads.s3.amazonaws.com/i/85shaymb7c1p1jph4ug5.png)

But whey does Django best practice say avoid naked settings and instead use `django.conf.settings`? A simple answer is naked `settings.py` will not have the Django [default values](https://github.com/django/django/blob/master/django/conf/global_settings.py) and can trigger a race condition, but there is more to it than that.

Overriding settings in tests is simplified too when using `django.conf.settings` as we can then use Django's [`modify_settings`](https://docs.djangoproject.com/en/3.1/topics/testing/tools/#django.test.modify_settings) and django-pytest's [`settings`](https://pytest-django.readthedocs.io/en/latest/helpers.html#settings) fixture. This helps avoid unmaintainable flaky tests as one test's changes cannot affect another because changes are reset after the test finishes.

To understand how that works we need to look deeper at what `django.conf.settings` is exactly.

## Proxy for a swappable sources of truth
`django.conf.settings` is not a module. It's an instance of [`LazySettings`](https://github.com/django/django/blob/master/django/conf/__init__.py#L49). This object is a wrapper around a "holder" of the actual settings. During the normal running of your Django app, that holder ingests the values in the file defined by the environment variable `DJANGO_SETTINGS_MODULE`, which you will recognize from [`manage.py`](https://github.com/django/django/blob/84ca7b860223d9211976be1a179c727784defc7a/tests/i18n/sampleproject/manage.py#L8), or if you ever tried importing Django from the python shell without first setting that variable and getting the classic

```sh
django.core.exceptions.ImproperlyConfigured: Requested setting
INSTALLED_APPS, but settings are not configured. You must either
define the environment variable DJANGO_SETTINGS_MODULE
or call settings.configure() before accessing settings.
```


As the name "lazy" implies, the values from `DJANGO_SETTINGS_MODULE` are not evaluated [until they are interacted with](https://github.com/django/django/blob/master/django/conf/__init__.py#L82). This is how we can import settings throughout our codebase with confidence that we will not get an error that the app is not ready yet.

So we can see, `django.conf.settings` is not the same thing as the `settings.py` file: `django.conf.settings` is a _normally_ a proxy around your `settings.py` file, but not always.

## UserSettingsHolder
During tests it's convenient to use very different values to those in your `settings.py`, or maybe the project does not have a `settings.py` (such as a third-party library).

For these cases we can [explicitly call](https://pytest-django.readthedocs.io/en/latest/configuring_django.html#using-django-conf-settings-configure) `settings.cofigure()`. When that happens the `django.conf.settings`'s holder will be a [`UserSettingsHolder`](https://github.com/django/django/blob/84ca7b860223d9211976be1a179c727784defc7a/django/conf/__init__.py#L229), and not interact with `settings.py` at all.

So there are a few great reasons to use `from django.conf import settings` instead of importing the `settings.py` directly:

- You get Django's default values set
- It simplifies safely overriding settings during tests
- It avoid triggering _app is not ready_ exceptions

Beware though that lowercase values in your `settings.py` are not exposed to `django.conf.settings`, but you can store those values in some other files so not a huge loss.

### Does your codebase import settings.py directly?

Over time it's easy for tech debt to slip into your codebase. I can check that for you at [django.doctor](https://django.doctor), or can [review your GitHub PRs](https://github.com/marketplace/django-doctor/):

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/d10epulva9d373jgldhu.png)