Skip to content

Add custom LOADER_CLASS support #210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}
}
```
Expand Down Expand Up @@ -168,6 +169,40 @@ and your webpack config is located at `/home/src/webpack.config.js`, then the va

<br>

#### 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/',
}
}
```

<br>

## Usage
<br>
Expand Down
63 changes: 63 additions & 0 deletions tests/app/tests/test_custom_loaders.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion webpack_loader/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__author__ = 'Owais Lone'
__version__ = '0.6.0'
__version__ = '0.7.0'

default_app_config = 'webpack_loader.apps.WebpackLoaderConfig'
3 changes: 2 additions & 1 deletion webpack_loader/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}
}

Expand Down
11 changes: 5 additions & 6 deletions webpack_loader/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down
23 changes: 20 additions & 3 deletions webpack_loader/utils.py
Original file line number Diff line number Diff line change
@@ -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]


Expand Down