diff --git a/docs/features/utilities.md b/docs/features/utilities.md new file mode 100644 index 00000000..e4d5191f --- /dev/null +++ b/docs/features/utilities.md @@ -0,0 +1,17 @@ +## Get Component + +You can fetch any of your IDOM components from other files by using `get_component`. + +```python title="components.py" +from idom import component +from django_idom.utils import get_component + +@component +def MyComponent(): + hello_world = get_component("example_project.my_app.components.HelloWorld") + return hello_world(recipient="World") +``` + +??? question "Should I use this instead of `#!python import`?" + + TBD \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 1cfff5ab..fbf664ab 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,6 +11,7 @@ nav: - Exclusive Features: - Hooks: features/hooks.md - Template Tag: features/templatetag.md + - Utilities: features/utilities.md - Contribute: - Code: contribute/django-idom.md - Docs: contribute/docs.md diff --git a/requirements/test-env.txt b/requirements/test-env.txt index 6f2e151e..61ee65ee 100644 --- a/requirements/test-env.txt +++ b/requirements/test-env.txt @@ -1,3 +1,3 @@ django -selenium +selenium <= 4.2.0 twisted diff --git a/src/django_idom/__init__.py b/src/django_idom/__init__.py index af2dec38..f936b88c 100644 --- a/src/django_idom/__init__.py +++ b/src/django_idom/__init__.py @@ -1,7 +1,7 @@ -from . import hooks +from . import hooks, utils from .websocket.consumer import IdomWebsocket from .websocket.paths import IDOM_WEBSOCKET_PATH __version__ = "1.0.0" -__all__ = ["IDOM_WEBSOCKET_PATH", "IdomWebsocket", "hooks"] +__all__ = ["IDOM_WEBSOCKET_PATH", "IdomWebsocket", "hooks", "utils"] diff --git a/src/django_idom/apps.py b/src/django_idom/apps.py index 6a963664..84aa1891 100644 --- a/src/django_idom/apps.py +++ b/src/django_idom/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -from django_idom.utils import ComponentPreloader +from django_idom.utils import _ComponentPreloader class DjangoIdomConfig(AppConfig): @@ -8,4 +8,4 @@ class DjangoIdomConfig(AppConfig): def ready(self): # Populate the IDOM component registry when Django is ready - ComponentPreloader().register_all() + _ComponentPreloader().register_all() diff --git a/src/django_idom/config.py b/src/django_idom/config.py index cb037ed1..7d8cd294 100644 --- a/src/django_idom/config.py +++ b/src/django_idom/config.py @@ -2,10 +2,10 @@ from django.conf import settings from django.core.cache import DEFAULT_CACHE_ALIAS, caches -from idom.core.types import ComponentConstructor +from idom.core.types import ComponentType -IDOM_REGISTERED_COMPONENTS: Dict[str, ComponentConstructor] = {} +IDOM_REGISTERED_COMPONENTS: Dict[str, ComponentType] = {} IDOM_WEBSOCKET_URL = getattr(settings, "IDOM_WEBSOCKET_URL", "idom/") IDOM_WS_MAX_RECONNECT_TIMEOUT = getattr( diff --git a/src/django_idom/templatetags/idom.py b/src/django_idom/templatetags/idom.py index 5707733d..9a2c43ac 100644 --- a/src/django_idom/templatetags/idom.py +++ b/src/django_idom/templatetags/idom.py @@ -6,7 +6,7 @@ from django.urls import reverse from django_idom.config import IDOM_WEBSOCKET_URL, IDOM_WS_MAX_RECONNECT_TIMEOUT -from django_idom.utils import _register_component +from django_idom.utils import register_component IDOM_WEB_MODULES_URL = reverse("idom:web_modules", args=["x"])[:-1][1:] @@ -15,7 +15,7 @@ @register.inclusion_tag("idom/component.html") def component(_component_id_, **kwargs): - _register_component(_component_id_) + register_component(_component_id_) class_ = kwargs.pop("class", "") json_kwargs = json.dumps(kwargs, separators=(",", ":")) diff --git a/src/django_idom/utils.py b/src/django_idom/utils.py index 81783013..fc82b0ab 100644 --- a/src/django_idom/utils.py +++ b/src/django_idom/utils.py @@ -4,10 +4,11 @@ import re from fnmatch import fnmatch from importlib import import_module -from typing import Set +from typing import Set, Union from django.template import engines from django.utils.encoding import smart_str +from idom.types import ComponentType from django_idom.config import IDOM_REGISTERED_COMPONENTS @@ -16,11 +17,20 @@ _logger = logging.getLogger(__name__) -def _register_component(full_component_name: str) -> None: - if full_component_name in IDOM_REGISTERED_COMPONENTS: +def get_component(dotted_path: str) -> Union[ComponentType, None]: + """Fetches a component given it's Python dotted path, if it exists.""" + try: + return IDOM_REGISTERED_COMPONENTS[dotted_path] + except KeyError: + _logger.info("A component named %s was never registered!", dotted_path) + + +def register_component(dotted_path: str) -> None: + """Registers a component given it's Python dotted path.""" + if dotted_path in IDOM_REGISTERED_COMPONENTS: return - module_name, component_name = full_component_name.rsplit(".", 1) + module_name, component_name = dotted_path.rsplit(".", 1) try: module = import_module(module_name) @@ -36,11 +46,11 @@ def _register_component(full_component_name: str) -> None: f"Module {module_name!r} has no component named {component_name!r}" ) from error - IDOM_REGISTERED_COMPONENTS[full_component_name] = component - _logger.debug("IDOM has registered component %s", full_component_name) + IDOM_REGISTERED_COMPONENTS[dotted_path] = component + _logger.debug("IDOM has registered component %s", dotted_path) -class ComponentPreloader: +class _ComponentPreloader: def register_all(self): """Registers all IDOM components found within Django templates.""" # Get all template folder paths @@ -123,7 +133,7 @@ def _register_components(self, components: Set) -> None: for component in components: try: _logger.info("IDOM preloader has detected component %s", component) - _register_component(component) + register_component(component) except Exception: _logger.error( "\033[91m" diff --git a/tests/test_app/tests/test_utils.py b/tests/test_app/tests/test_utils.py new file mode 100644 index 00000000..4f189c91 --- /dev/null +++ b/tests/test_app/tests/test_utils.py @@ -0,0 +1,13 @@ +from django.test import TestCase + +from django_idom.utils import get_component + + +class RegexTests(TestCase): + def test_get_component(self): + real_component = get_component("test_app.components.HelloWorld") + self.assertIsNotNone(real_component) + self.assertEqual(getattr(real_component, "__name__", None), "HelloWorld") + + fake_component = get_component("test_app.components.FakeComponent") + self.assertIsNone(fake_component)