Why am I getting KeyError: "Django settings doesn't define RESOLVER"?

1.2k Views Asked by At

I am getting the error below:

(testassets)➜ testassets git:(master) ✗ django-admin.py test Creating test database for alias 'default'... E ====================================================================== ERROR: test_get_site_root_with_settings_overrides (app.tests.AssetsTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Volumes/fifteen5cs/testassets/app/tests.py", line 27, in test_get_site_root_with_settings_overrides http_client.get('/') File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/django/test/client.py", line 473, in get response = super(Client, self).get(path, data=data, **extra) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/django/test/client.py", line 280, in get return self.request(**r) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/django/test/client.py", line 444, in request six.reraise(*exc_info) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/django/core/handlers/base.py", line 114, in get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/Volumes/fifteen5cs/testassets/app/views.py", line 9, in index context_instance=RequestContext(request)) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/django/shortcuts/init.py", line 29, in render_to_response return HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/django/template/loader.py", line 169, in render_to_string return t.render(context_instance) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/django/template/base.py", line 140, in render return self._render(context) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/django/test/utils.py", line 85, in instrumented_test_render return self.nodelist.render(context) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/django/template/base.py", line 840, in render bit = self.render_node(node, context) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/django/template/debug.py", line 78, in render_node return node.render(context) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/django_assets/templatetags/assets.py", line 72, in render for url in bundle.urls(): File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/webassets/bundle.py", line 783, in urls for bundle, extra_filters, new_ctx in self.iterbuild(ctx): File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/webassets/bundle.py", line 679, in iterbuild for bundle, _ in self.resolve_contents(ctx): File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/webassets/bundle.py", line 233, in resolve_contents result = ctx.resolver.resolve_source(ctx, item) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/webassets/bundle.py", line 50, in getattr return self.getattr(self._parent, item) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/webassets/bundle.py", line 58, in getattr return getattr(object, item) File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/webassets/env.py", line 675, in _get_resolver return self._storage['resolver'] File "/Users/paul/.pyenv/versions/testassets/lib/python2.7/site-packages/django_assets/env.py", line 62, in getitem self._transform_key(key)) KeyError: "Django settings doesn't define RESOLVER"

----------------------------------------------------------------------
Ran 1 test in 0.325s

FAILED (errors=1)
Destroying test database for alias 'default'...

I have semi-linked this error to the use of the Django utils function django.test.utils.override_settings within one of my unit tests (shown below)

  1 from django.test.utils import override_settings
  2 from django.utils.unittest.case import TestCase
  3 from django.test.client import Client
  4
  5
  6 OVERRIDE_SETTINGS = {
  7     'DEBUG': True,
  8     'ASSETS_DEBUG': True,
  9     'ASSETS_AUTO_BUILD': True,
 10     'ASSETS_URL_EXPIRE': False,
 11     'ASSETS_CACHE': False,
 12     'ASSETS_MANIFEST': False,
 13     'ASSETS_VERSIONS': False,
 14 }
 15
 16
 17 class AssetsTestCase(TestCase):
 18     def test_get_site_root_with_settings_overrides(self):
 19         http_client = Client()
 20         # import pdb;pdb.set_trace()
 21         settings_override = override_settings(**OVERRIDE_SETTINGS)
 22         settings_override.enable()
 23         http_client.get('/')
 24         settings_override.disable()
 25
 26         settings_override.enable()
 27         http_client.get('/')
 28         settings_override.disable()

(NB. The exception is raised during the second request!)

The code base that I am working on that first presented this issue is too large and private to share so I have stripped down the project to a small amount of code that still creates the issue. That mini project can be found here https://github.com/logston/testassets.

I have spent over two days trying to determine where exactly this error is coming from and why it occurs during the second request but not the first. I have tried a number of permutations of unit tests. Interestingly, if I create a second unit test, one that does not enable settings overrides (eg. like the one below) and name that test such that it runs first during testing, the test suite passes. If I place that same unit test after the test_get_site_root_with_settings_overrides unit test, both will fail.

      def test_get_site_root(self):
          http_client = Client()
          http_client.get('/')

          http_client.get('/')

Any help on this issue would be immensely appreciated.

Finally, the only issue that I can find that speaks of the same or a similar problem is here: https://github.com/miracle2k/django-assets/issues/44

UPDATE 2015/01/12

The issue seems to be related to the use of signals. I have stripped the above failing test down to the following:

from django.test.utils import override_settings
from django.utils.unittest.case import TestCase
from django_assets.env import get_env


class AssetsTestCase(TestCase):
    def test(self):
        settings_override = override_settings()
        settings_override.enable()
        get_env().resolver
        settings_override.disable()

        settings_override.enable()
        get_env().resolver
        settings_override.disable()
1

There are 1 best solutions below

0
On

Turns out this issue is due to the fact that the django_assets.env.env singleton is not reset after disabling any override_settings. The fact that this singleton object is not rebuilt between 'settings' objects means that if a django_assets.env.env object is built in the context of overridden settings, when those overridden settings are swapped out for the non-overridden settings, any constants added to the interim settings module by the creation of the django_assets.env.env object will be lost. RESOLVER and ASSETS_CACHE are great examples of constants that will be lost. To avoid this loss, we have to be sure to reset the django_assets.env.env object by calling django_assets.env.reset after a change in settings modules. Calling reset will force django-assets to reinsert these constants back into the current settings modules the next time django_assets.env.get_env is called.

Unfortunately, calling django_assets.env.reset causes the django_assets.env.env._bundle_names dictionary to be emptied (nb. really its destroyed and a new one is built). The loss of this dictionary causes errors like the following:

BundleError: %s not found (using staticfiles finders)

To fix this issue, we have to update django_assets.env._ASSETS_LOADED to False and remove each app's assets.py file from sys.modules. We have to update _ASSETS_LOADED so that django-assets attempts to reimport each app's assets file the next time django_assets.env.get_env is called. The call to django_assets.env.get_env will also rebuild the env._bundle_names dictionary. Finally, we have to remove each app's assets module from sys.modules. Otherwise, __import__('app.assets') will not import (read 'execute') the assets module because the assets module is already imported!

Thus, a complete solution to this question would be something like the following:

import sys

from django_assets import env as assets_env

settings_override = override_settings(**OVERRIDE_SETTINGS)
settings_override.enable()
... do things ...
settings_override.disable()
assets_env.reset()
assets_env._ASSETS_LOADED = False
del sys.modules['<app_name>.assets']

BTW, I am all ears for suggestions on other ways to solve this issue.