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]