-
-
Notifications
You must be signed in to change notification settings - Fork 22
Auto populate IDOM component registry #24
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
Archmonger
merged 23 commits into
reactive-python:main
from
Archmonger:populate-idom-components
Nov 2, 2021
Merged
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
9b0f382
Auto populate IDOM registered components
Archmonger 7bf6842
fix styling
Archmonger e6e28ea
remove noqa
Archmonger 80e6bd8
use regex instead of render
Archmonger 419f5c1
string cleanup
Archmonger f3dbd6d
more robust matching
Archmonger 00b7323
remove unneeded escape slashes
Archmonger 7cf9aab
move _register_component to utils
Archmonger 3368eaa
use absolute django idom import path
Archmonger 30ad813
remove unused import
Archmonger 8b500cb
add logging to auto registration
Archmonger f2c6096
clean up docstring and comments
Archmonger 7c84ef3
remove unneeded sys.modules check
Archmonger daabf62
avoid multiple component registrations
Archmonger afe643e
use in instead of get
Archmonger 5b2e619
fix _get_components docstring
Archmonger bc1666e
add type hints
Archmonger fa46d50
fix appconfig name
Archmonger d030b86
add component regex tests
Archmonger b7ea800
move selenium tests to folder
Archmonger 7a9ba2d
precompile component regex
Archmonger 6142cce
use for loop for testing regex
Archmonger 9da3d52
Merge remote-tracking branch 'upstream/main' into populate-idom-compo…
Archmonger File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from django.apps import AppConfig | ||
|
||
from django_idom.utils import ComponentPreloader | ||
|
||
|
||
class DjangoIdomConfig(AppConfig): | ||
name = "django_idom" | ||
|
||
def ready(self): | ||
# Populate the IDOM component registry when Django is ready | ||
ComponentPreloader().register_all() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import logging | ||
import os | ||
import re | ||
from fnmatch import fnmatch | ||
from importlib import import_module | ||
from typing import Set | ||
|
||
from django.template import engines | ||
from django.utils.encoding import smart_str | ||
|
||
from django_idom.config import IDOM_REGISTERED_COMPONENTS | ||
|
||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
def _register_component(full_component_name: str) -> None: | ||
if full_component_name in IDOM_REGISTERED_COMPONENTS: | ||
return | ||
|
||
module_name, component_name = full_component_name.rsplit(".", 1) | ||
|
||
try: | ||
module = import_module(module_name) | ||
except ImportError as error: | ||
raise RuntimeError( | ||
f"Failed to import {module_name!r} while loading {component_name!r}" | ||
) from error | ||
|
||
try: | ||
component = getattr(module, component_name) | ||
except AttributeError as error: | ||
raise RuntimeError( | ||
f"Module {module_name!r} has no component named {component_name!r}" | ||
) from error | ||
|
||
IDOM_REGISTERED_COMPONENTS[full_component_name] = component | ||
|
||
|
||
class ComponentPreloader: | ||
def register_all(self): | ||
"""Registers all IDOM components found within Django templates.""" | ||
# Get all template folder paths | ||
paths = self._get_paths() | ||
# Get all HTML template files | ||
templates = self._get_templates(paths) | ||
# Get all components | ||
components = self._get_components(templates) | ||
# Register all components | ||
self._register_components(components) | ||
|
||
def _get_loaders(self): | ||
"""Obtains currently configured template loaders.""" | ||
template_source_loaders = [] | ||
for e in engines.all(): | ||
if hasattr(e, "engine"): | ||
template_source_loaders.extend( | ||
e.engine.get_template_loaders(e.engine.loaders) | ||
) | ||
loaders = [] | ||
for loader in template_source_loaders: | ||
if hasattr(loader, "loaders"): | ||
loaders.extend(loader.loaders) | ||
else: | ||
loaders.append(loader) | ||
return loaders | ||
|
||
def _get_paths(self) -> Set: | ||
"""Obtains a set of all template directories.""" | ||
paths = set() | ||
for loader in self._get_loaders(): | ||
try: | ||
module = import_module(loader.__module__) | ||
get_template_sources = getattr(module, "get_template_sources", None) | ||
if get_template_sources is None: | ||
get_template_sources = loader.get_template_sources | ||
paths.update(smart_str(origin) for origin in get_template_sources("")) | ||
except (ImportError, AttributeError, TypeError): | ||
pass | ||
|
||
return paths | ||
|
||
def _get_templates(self, paths: Set) -> Set: | ||
"""Obtains a set of all HTML template paths.""" | ||
extensions = [".html"] | ||
templates = set() | ||
for path in paths: | ||
for root, dirs, files in os.walk(path, followlinks=False): | ||
templates.update( | ||
os.path.join(root, name) | ||
for name in files | ||
if not name.startswith(".") | ||
and any(fnmatch(name, "*%s" % glob) for glob in extensions) | ||
) | ||
|
||
return templates | ||
|
||
def _get_components(self, templates: Set) -> Set: | ||
"""Obtains a set of all IDOM components by parsing HTML templates.""" | ||
component_regex = re.compile( | ||
r"{% *idom_component ((\"[^\"']*\")|('[^\"']*')).*%}" | ||
) | ||
Archmonger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
components = set() | ||
for template in templates: | ||
try: | ||
with open(template, "r", encoding="utf-8") as template_file: | ||
match = component_regex.findall(template_file.read()) | ||
if not match: | ||
continue | ||
components.update( | ||
[group[0].replace('"', "").replace("'", "") for group in match] | ||
) | ||
except Exception: | ||
pass | ||
|
||
return components | ||
|
||
def _register_components(self, components: Set) -> None: | ||
"""Registers all IDOM components in an iterable.""" | ||
for component in components: | ||
try: | ||
_register_component(component) | ||
_logger.info("IDOM has registered component %s", component) | ||
except Exception: | ||
_logger.warning("IDOM failed to register component %s", component) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.