diff --git a/README.md b/README.md
index a3e395c1..6f35da1b 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,8 @@ WEBPACK_LOADER = {
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
'POLL_INTERVAL': 0.1,
'TIMEOUT': None,
- 'IGNORE': [r'.+\.hot-update.js', r'.+\.map']
+ 'IGNORE': [r'.+\.hot-update.js', r'.+\.map'],
+ 'LOADER_CLASS': 'webpack_loader.loader.WebpackLoader',
}
}
```
@@ -168,6 +169,40 @@ and your webpack config is located at `/home/src/webpack.config.js`, then the va
+#### LOADER_CLASS
+
+`LOADER_CLASS` is the fully qualified name of a python class as a string that holds the custom webpack loader.
+This is where behavior can be customized as to how the stats file is loaded. Examples include loading the stats file
+from a database, cache, external url, etc. For convenience, `webpack_loader.loader.WebpackLoader` can be extended;
+The `load_assets` method is likely where custom behavior will be added. This should return the stats file as an object.
+
+Here's a simple example of loading from an external url:
+
+```py
+# in app.module
+import requests
+from webpack_loader.loader import WebpackLoader
+
+class ExternalWebpackLoader(WebpackLoader):
+
+ def load_assets(self):
+ url = self.config['STATS_URL']
+ return requests.get(url).json()
+
+
+# in app.settings
+WEBPACK_LOADER = {
+ 'DEFAULT': {
+ 'CACHE': False,
+ 'BUNDLE_DIR_NAME': 'bundles/',
+ 'LOADER_CLASS': 'app.module.ExternalWebpackLoader',
+ # Custom config setting made available in WebpackLoader's self.config
+ 'STATS_URL': 'https://www.test.com/path/to/stats/',
+ }
+}
+```
+
+
## Usage
diff --git a/tests/app/tests/test_custom_loaders.py b/tests/app/tests/test_custom_loaders.py
new file mode 100644
index 00000000..83cb24b3
--- /dev/null
+++ b/tests/app/tests/test_custom_loaders.py
@@ -0,0 +1,63 @@
+from imp import reload
+from django.test import TestCase
+from webpack_loader import utils, config, loader
+
+
+DEFAULT_CONFIG = 'DEFAULT'
+LOADER_PAYLOAD = {'status': 'done', 'chunks': []}
+
+
+class ValidCustomLoader(loader.WebpackLoader):
+
+ def load_assets(self):
+ return LOADER_PAYLOAD
+
+
+class CustomLoadersTestCase(TestCase):
+ def tearDown(self):
+ self.reload_webpack()
+
+ def reload_webpack(self):
+ '''
+ Reloads webpack loader modules that have cached values to avoid polluting certain tests
+ '''
+ reload(utils)
+ reload(config)
+
+ def test_bad_custom_loader(self):
+ '''
+ Tests that a bad custom loader path will raise an error
+ '''
+ loader_class = 'app.tests.bad_loader_path.BadCustomLoader'
+ with self.settings(WEBPACK_LOADER={
+ 'DEFAULT': {
+ 'CACHE': False,
+ 'BUNDLE_DIR_NAME': 'bundles/',
+ 'LOADER_CLASS': loader_class
+ }
+ }):
+ self.reload_webpack()
+ try:
+ loader = utils.get_loader(DEFAULT_CONFIG)
+ self.fail('The loader should fail to load with a bad LOADER_CLASS')
+ except ImportError as e:
+ self.assertIn(
+ '{} doesn\'t look like a valid module path'.format(loader_class),
+ str(e)
+ )
+
+ def test_good_custom_loader(self):
+ '''
+ Tests that a good custom loader will return the correct assets
+ '''
+ loader_class = 'app.tests.test_custom_loaders.ValidCustomLoader'
+ with self.settings(WEBPACK_LOADER={
+ 'DEFAULT': {
+ 'CACHE': False,
+ 'BUNDLE_DIR_NAME': 'bundles/',
+ 'LOADER_CLASS': loader_class,
+ }
+ }):
+ self.reload_webpack()
+ assets = utils.get_loader(DEFAULT_CONFIG).load_assets()
+ self.assertEqual(assets, LOADER_PAYLOAD)
diff --git a/webpack_loader/__init__.py b/webpack_loader/__init__.py
index d8c6d476..539a6f7f 100644
--- a/webpack_loader/__init__.py
+++ b/webpack_loader/__init__.py
@@ -1,4 +1,4 @@
__author__ = 'Owais Lone'
-__version__ = '0.6.0'
+__version__ = '0.7.0'
default_app_config = 'webpack_loader.apps.WebpackLoaderConfig'
diff --git a/webpack_loader/config.py b/webpack_loader/config.py
index 9f334b4a..213df350 100644
--- a/webpack_loader/config.py
+++ b/webpack_loader/config.py
@@ -14,7 +14,8 @@
# FIXME: Explore usage of fsnotify
'POLL_INTERVAL': 0.1,
'TIMEOUT': None,
- 'IGNORE': [r'.+\.hot-update.js', r'.+\.map']
+ 'IGNORE': ['.+\.hot-update.js', '.+\.map'],
+ 'LOADER_CLASS': 'webpack_loader.loader.WebpackLoader',
}
}
diff --git a/webpack_loader/loader.py b/webpack_loader/loader.py
index b366dc3a..de76e721 100644
--- a/webpack_loader/loader.py
+++ b/webpack_loader/loader.py
@@ -11,17 +11,16 @@
WebpackLoaderTimeoutError,
WebpackBundleLookupError
)
-from .config import load_config
class WebpackLoader(object):
_assets = {}
- def __init__(self, name='DEFAULT'):
+ def __init__(self, name, config):
self.name = name
- self.config = load_config(self.name)
+ self.config = config
- def _load_assets(self):
+ def load_assets(self):
try:
with open(self.config['STATS_FILE'], encoding="utf-8") as f:
return json.load(f)
@@ -34,9 +33,9 @@ def _load_assets(self):
def get_assets(self):
if self.config['CACHE']:
if self.name not in self._assets:
- self._assets[self.name] = self._load_assets()
+ self._assets[self.name] = self.load_assets()
return self._assets[self.name]
- return self._load_assets()
+ return self.load_assets()
def filter_chunks(self, chunks):
for chunk in chunks:
diff --git a/webpack_loader/utils.py b/webpack_loader/utils.py
index e7b7b2f3..c501cc63 100644
--- a/webpack_loader/utils.py
+++ b/webpack_loader/utils.py
@@ -1,14 +1,31 @@
+from importlib import import_module
from django.conf import settings
-
-from .loader import WebpackLoader
+from .config import load_config
_loaders = {}
+def import_string(dotted_path):
+ '''
+ This is a rough copy of django's import_string, which wasn't introduced until Django 1.7
+
+ Once this package's support for Django 1.6 has been removed, this can be safely replaced with
+ `from django.utils.module_loading import import_string`
+ '''
+ try:
+ module_path, class_name = dotted_path.rsplit('.', 1)
+ module = import_module(module_path)
+ return getattr(module, class_name)
+ except (ValueError, AttributeError, ImportError):
+ raise ImportError('%s doesn\'t look like a valid module path' % dotted_path)
+
+
def get_loader(config_name):
if config_name not in _loaders:
- _loaders[config_name] = WebpackLoader(config_name)
+ config = load_config(config_name)
+ loader_class = import_string(config['LOADER_CLASS'])
+ _loaders[config_name] = loader_class(config_name, config)
return _loaders[config_name]