miracle2k / django-assets

Django webassets integration.
BSD 2-Clause "Simplified" License
89 stars 79 forks source link

Missing resolver when running tests on upgrade to 0.10 #44

Open hexsprite opened 10 years ago

hexsprite commented 10 years ago

TLDR - getting error: KeyError: "Django settings doesn't define RESOLVER"

Have this in my requirements.txt:

django-assets==0.10 webassets==0.10.1

Django 1.6.4

When running tests I get this error:

...
  File "/Users/jbb/.virtualenvs/fifteen5/lib/python2.7/site-packages/django_assets/templatetags/assets.py", line 72, in render
    for url in bundle.urls():
  File "/Users/jbb/.virtualenvs/fifteen5/lib/python2.7/site-packages/webassets/bundle.py", line 786, in urls
    for bundle, extra_filters, new_ctx in self.iterbuild(ctx):
  File "/Users/jbb/.virtualenvs/fifteen5/lib/python2.7/site-packages/webassets/bundle.py", line 682, in iterbuild
    for bundle, _ in self.resolve_contents(ctx):
  File "/Users/jbb/.virtualenvs/fifteen5/lib/python2.7/site-packages/webassets/bundle.py", line 233, in resolve_contents
    result = ctx.resolver.resolve_source(ctx, item)
  File "/Users/jbb/.virtualenvs/fifteen5/lib/python2.7/site-packages/webassets/bundle.py", line 50, in __getattr__
    return self.getattr(self._parent, item)
  File "/Users/jbb/.virtualenvs/fifteen5/lib/python2.7/site-packages/webassets/bundle.py", line 58, in getattr
    return getattr(object, item)
  File "/Users/jbb/.virtualenvs/fifteen5/lib/python2.7/site-packages/webassets/env.py", line 675, in _get_resolver
    return self._storage['resolver']
  File "/Users/jbb/.virtualenvs/fifteen5/lib/python2.7/site-packages/django_assets/env.py", line 62, in __getitem__
    self._transform_key(key))
KeyError: "Django settings doesn't define RESOLVER"

In my test setup I use Django's override_settings to configure a test environment like so:

        DEBUG=True,
        ASSETS_DEBUG=True,
        ASSETS_AUTO_BUILD=True,
        ASSETS_URL_EXPIRE=False,
        ASSETS_UPDATER=None,
        ASSETS_CACHE=False,
        ASSETS_MANIFEST=False,
        ASSETS_VERSIONS=False,
logston commented 9 years ago

I am having the same issue. I have posted it on stackoverflow. @miracle2k, have you seen this before?

http://stackoverflow.com/questions/27895156/why-am-i-getting-keyerror-django-settings-doesnt-define-resolver

logston commented 9 years ago

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 ticket's issue 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']
bboogaard commented 9 years ago

What's the status of this issue. I'm (still) having the same problem after the upgrade to 0.10

logston commented 9 years ago

Not sure. Not working on this issue anymore.

logston commented 9 years ago

Never fixed it for myself.

bboogaard commented 9 years ago

Ok, well, your suggestion in my test classes after settings_override.disable() fixes my problem, though it's a bit of a hack. My thanks to you!

willharris commented 9 years ago

Also having the same problem.

JanChec commented 9 years ago

+1

adamchainz commented 9 years ago

Advanced workaround:

Django exposes a setting_changed signal that can be used to perform this reset. In 1.7 it's django.test.signals.setting_changed ( https://github.com/django/django/blob/1.7/django/test/signals.py#L14 ), in 1.8+ it's django.core.signals.setting_changed ( https://github.com/django/django/blob/1.8/django/core/signals.py#L6 ). You can then put this in the bottom of one of your apps.py files:

def fix_assets(*args, **kwargs):
    from django_assets import env as assets_env
    assets_env.reset()
    assets_env._ASSETS_LOADED = False

    # Search for assets modules that need reloading - n.b. this isn't the proper way of doing it
    for name in settings.INSTALLED_APPS:
        assets_module = name + '.assets'
        if assets_module in sys.modules:
            del sys.modules[assets_module]

and then in your app config's ready() you can connect to the signal:

class MyAppConfig(AppConfig):
    def ready(self):
        from django.test.signals import setting_changed
        setting_changed.connect(fix_assets)

This is half the code needed to implement a proper patch - depends on the reloading though. Will have a look at writing a PR soon.

miracle2k commented 9 years ago

Just some preliminary thoughts:

I've had other issues with the fact that webassets wants to push the actual object instances of resolver, updater etc. to the settings object; in that case, the settings backend only supported string values.

I think the issue here is fundamentally with the design of the settings backend API. It exists, because we want integration modules like django-assets to allow configuration via the native settings systems.

If in Django, it is possible to switch out the settings objects completely, the django-assets backend needs to accommodate that. Maybe by listening to the settings_changed signal and reapplying it's default values.

If the settings backend API of webassets core needs to be changed to make this possible, so be it. I wonder right now, for instance, if it is sound that webassets core wants to write the defaults for it's settings to the settings storage directly when the environment is created, of if it should fall back to the defaults, on access, when the settings backend does not have a value for a setting.

adamchainz commented 9 years ago

Every other django library I can remember looking at falls back to defaults on-access. Maybe webassets should change.

That said if you can safely re-init the enviroment object on settings_changed, that would be simpler.

AlecRosenbaum commented 6 years ago

For the record, I'm still seeing this issue in Django 1.11 with django-assets==0.12. It's only intermittent, and only seems to effect integration testing using pytest and Django's StaticLiveServerTestCase.

I will likely be adding @adamchainz's fix_assets patch to the setUp of my integration test base class for now. @miracle2k Are there any plans for a proper fix?

sonnybaker commented 5 years ago

I've started getting this same error, seemingly at random, on a production server but not during testing, so the fix_assets patch isn't relevant. It started happening on a site that has been running smoothly for over a year. I can't think of any changes to assets or settings that have happened recently. Restarting the supervisor process resolves the issue, but it's causing site-wide 500 errors until that happens. Stacktrace just points to the template level assets code. Any clue what this could be?

Using versions:

Django==2.0.8
django-assets==0.12
webassets==0.12.1

Settings:

ASSETS_DEBUG = False
ASSETS_AUTO_BUILD = False