Would someone please help me understand python import behavior and how to test it correctly? When I re-import settings in a test (using mocked environs), the imported settings or their sub-imports are cached versions from sys.modules or elsewhere. I'm having trouble reproducing the same behavior that from my_project.app import settings creates on first loading.
My tests look like,
import importlib
import logging
import os
import sys
from unittest import mock
from django.test import TestCase
from my_project.app.config import settings
all_settings = [
'my_project.app.config.shared_settings',
'my_project.app.config.local_settings',
...,
]
def remove_old_settings():
importlib.invalidate_caches()
for setting in all_settings:
try:
del sys.modules[setting]
except KeyError:
pass
class DjangoSettingsTestCase(TestCase):
def setUpClass():
logging.disable(logging.CRITICAL)
remove_old_settings()
importlib.reload(settings)
def tearDownClass():
remove_old_settings()
importlib.reload(settings)
logging.disable(logging.NOTSET)
def get_settings_with_env(self, env):
environ_keys = [
'DJANGO_DEPLOYMENT',
'deployment',
'DATABASES',
'CACHES',
'ALLOWED_HOSTS',
'DEBUG',
'STATIC_URL',
'CORS_ORIGIN_ALLOW_ALL',
]
with mock.patch.dict('os.environ', env, clear=True):
globals()['settings'] = importlib.reload(settings)
values = {
key: getattr(settings, key)
for key in environ_keys
}
return values
def test_settings_django_env_localhost(self):
mocked_settings = self.get_settings_with_env({})
self.assertEqual(mocked_settings['deployment'], 'localhost')
mocked_settings = self.get_settings_with_env({'DJANGO_DEPLOYMENT': 'localhost'})
self.assertDjangoEnv(mocked_settings['deployment'], 'localhost')
self.assertDictEqual(
mocked_settings['DATABASES'],
{
'default': {
...
}
}
)
self.assertEqual(mocked_settings['CORS_ORIGIN_ALLOW_ALL'], True)
self.assertDictEqual(
mocked_settings['CACHES'],
{'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}}
)
self.assertListEqual(mocked_settings['ALLOWED_HOSTS'], ['*'])
self.assertEqual(mocked_settings['DEBUG'], True)
self.assertEqual(mocked_settings['STATIC_URL'], '/static/')
(and many more tests)
These tests usually pass but sometimes they don't. I've tried so many ways to import these files, but I'm at my wits end with different failures. As you can see from the crazy stuff I'm trying (deleting from sys.modules and globals) I'm at my wits end.
I maintain a Django project whose settings file uses conditional imports based on its deployment. The settings file has a structure like this,
# settings.py
import os
deployment = os.getenv('DJANGO_DEPLOYMENT')
if deployment == 'localhost':
from my_project.app.config.localhost_settings import *
elif deployment == 'staging':
from my_project.app.config.staging_settings import *
elif ...
else:
raise InvalidDjangoDeployment(deployment)
The deployment settings files look generally like this,
# local_settings.py
from my_project.app.config.shared_settings import *
DATABASES = {
'default': { ... },
...,
}
CACHES = ...
I had moderate success with using importlib.reload on all the modules that I would need to import (in reverse so as to make sure references to sb modules were fresh in higher levels). But I was worried that that approach would modify the settings module for later tests.