Skip to content

Commit

Permalink
"Merge pull request #453 from JDeuce/feature-yaml-custom-filter\n\nAl…
Browse files Browse the repository at this point in the history
…lowing custom filters to be specified in YAML"
  • Loading branch information
miracle2k committed Mar 12, 2016
2 parents 3e37b7b + 2d3febc commit 5379a8e
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 1 deletion.
11 changes: 11 additions & 0 deletions docs/custom_filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ the filter. First, you need to register the class with the system though:
from webassets.filter import register_filter
register_filter(NoopFilter)
Or if you are using yaml then use the filters key for the environment:
.. code-block:: yaml
directory: .
url: /
debug: True
updater: timestamp
filters:
- my_custom_package.my_filter
After that, you can use the filter like you would any of the built-in ones:
.. code-block:: django
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.pip
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ closure
slimit==0.8.1
ply==3.4 # https://github.com/rspivak/slimit/issues/76
libsass==0.8.3
zope.dottedname

# Python libs that requiring manual installation
#cssprefixer
31 changes: 31 additions & 0 deletions src/webassets/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os, sys
from os import path
import glob, fnmatch
import inspect
import types
from webassets import six
try:
Expand All @@ -17,6 +18,8 @@
from webassets import six
from webassets import Environment
from webassets.bundle import Bundle
from webassets.exceptions import EnvironmentError
from webassets.filter import register_filter
from webassets.importlib import import_module


Expand Down Expand Up @@ -103,6 +106,12 @@ def _open(self):
file = self.file_or_filename
return file, getattr(file, 'name', False)

@classmethod
def _get_import_resolver(cls):
""" method that can be overridden in tests """
from zope.dottedname.resolve import resolve as resolve_dotted
return resolve_dotted

def load_bundles(self, environment=None):
"""Load a list of :class:`Bundle` instances defined in the YAML file.
Expand Down Expand Up @@ -166,6 +175,8 @@ def load_environment(self):
url: /media
debug: True
updater: timestamp
filters:
- my_custom_package.my_filter
config:
compass_bin: /opt/compass
another_custom_config_value: foo
Expand Down Expand Up @@ -207,6 +218,26 @@ def load_environment(self):
path.join(path.dirname(filename),
env.config['directory']))

# Treat the 'filters' option special, it should resolve the
# entries as classes and register them to the environment
if 'filters' in obj:
try:
resolve_dotted = self._get_import_resolver()
except ImportError:
raise EnvironmentError(
"In order to use custom filters in the YAMLLoader "
"you must install the zope.dottedname package")
for filter_class in obj['filters']:
try:
cls = resolve_dotted(filter_class)
except ImportError:
raise LoaderError("Unable to resolve class %s" % filter_class)
if inspect.isclass(cls):
register_filter(cls)
else:
raise LoaderError("Custom filters must be classes "
"not modules or functions")

# Load custom config options
if 'config' in obj:
env.config.update(obj['config'])
Expand Down
81 changes: 80 additions & 1 deletion tests/test_loaders.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from __future__ import with_statement
import sys
from nose.tools import assert_raises
from nose.tools import assert_raises, assert_true
import textwrap
from webassets.env import Environment
from webassets.filter import Filter, get_filter
from webassets.utils import StringIO
from webassets.bundle import Bundle
from webassets.loaders import PythonLoader, YAMLLoader, LoaderError
from webassets.exceptions import EnvironmentError
from nose import SkipTest


Expand Down Expand Up @@ -186,3 +188,80 @@ def test_load_environment_with_prefix(self):
assert loader3.environment == "assets"
env3 = loader3.load_environment()
assert env3 == module2.assets

class TestYAMLCustomFilters(TestYAML):

def setup(self):
super(TestYAMLCustomFilters, self).setup()

# If zope.dottedname is not installed, that's OK
try:
import zope.dottedname.resolve
except ImportError:
raise SkipTest()
# Save off the original get_import_resolver
self.original_resolver = YAMLLoader._get_import_resolver
# Make a mock
def mock(cls):
raise ImportError
self.mock_resolver = mock

def mock_importer(self):
""" Mock the import resolver to a fake one that raises import error.
Be sure to call reset_importer if you use this at the beginning of
any test."""
YAMLLoader._get_import_resolver = self.mock_resolver

def reset_importer(self):
""" Reset the import resolver to the default one """
YAMLLoader._get_import_resolver = self.original_resolver

def test_cant_import_zope_is_fine(self):
""" Check that a YAML file without filters is fine if the import of
zope.dottedname fails """
self.mock_importer()
self.loader("""foo: bar""").load_environment()
self.reset_importer()

def test_need_zope_to_have_filter(self):
""" Check that we get an error if the zope.dottedname module is not
installed and they try to use custom filters """
self.mock_importer()
loader = self.loader("""
filters:
- webassets.filter.less.Less
""")
assert_raises(EnvironmentError, loader.load_environment)
self.reset_importer()

def test_load_filter_module_throws_exc(self):
""" Check that passing dotted module path throws an exception """
# Try to load based on module name, instead of the class
loader = self.loader("""
filters:
- webassets.filter.less
""")
assert_raises(LoaderError, loader.load_environment)

def test_bad_filter_throws_exc(self):
""" Test that importing filters that don't exist throws an exception """
loader = self.loader("""
filters:
- webassets.fake.filter
""")
assert_raises(LoaderError, loader.load_environment)

def test_load_filters(self):
"""Check that filters can be loaded from YAML """
# Delete the less filter
import webassets.filter
del webassets.filter._FILTERS['less']
# Verify that it was deleted
assert_raises(ValueError, get_filter, 'less')
# Load it again from YAML
self.loader("""
filters:
- webassets.filter.less.Less
""").load_environment()
# Check that it's back
assert_true(isinstance(get_filter('less'), Filter))

0 comments on commit 5379a8e

Please sign in to comment.