django-components/sampleproject/examples/utils.py
2025-10-05 09:09:08 +02:00

93 lines
2.9 KiB
Python

import importlib.util
import sys
from pathlib import Path
from typing import List, Set
from django.conf import settings
# Keep track of what we've already discovered to make subsequent calls a noop
_discovered_examples: Set[str] = set()
def discover_example_modules() -> List[str]:
"""
Find and import `component.py` and `page.py` files from example directories
`docs/examples/*/` (e.g. `docs/examples/form/component.py`).
These files will be importable from other modules like:
```python
from examples.dynamic.form.component import Form
# or
from examples.dynamic.form.page import FormPage
```
Components will be also registered with the ComponentRegistry, so they can be used
in the templates via the `{% component %}` tag like:
```django
{% component "form" / %}
```
This function is idempotent - calling it multiple times will not re-import modules.
"""
# Skip if we've already discovered examples
if _discovered_examples:
return list(_discovered_examples)
docs_examples_dir: Path = settings.EXAMPLES_DIR
if not docs_examples_dir.exists():
raise FileNotFoundError(f"Docs examples directory not found: {docs_examples_dir}")
for example_dir in docs_examples_dir.iterdir():
if not example_dir.is_dir():
continue
example_name = example_dir.name
component_file = example_dir / "component.py"
if component_file.exists():
_import_module_file(component_file, example_name, "component")
page_file = example_dir / "page.py"
if page_file.exists():
_import_module_file(page_file, example_name, "page")
# Mark this example as discovered
_discovered_examples.add(example_name)
return list(_discovered_examples)
def _import_module_file(py_file: Path, example_name: str, module_type: str):
"""
Dynamically import a python file as a module.
This file will then be importable from other modules like:
```python
from examples.dynamic.form.component import Form
# or
from examples.dynamic.form.page import FormPage
```
"""
module_name = f"examples.dynamic.{example_name}.{module_type}"
# Skip if module is already imported
if module_name in sys.modules:
return
try:
spec = importlib.util.spec_from_file_location(module_name, py_file)
if not spec or not spec.loader:
raise ValueError(f"Failed to load {module_type} {example_name}/{py_file.name}")
module = importlib.util.module_from_spec(spec)
# Add to sys.modules so the contents can be imported from other modules
# via Python import system.
sys.modules[module_name] = module
spec.loader.exec_module(module)
print(f"Loaded example {module_type}: {example_name}/{py_file.name}")
except Exception as e: # noqa: BLE001
print(f"Failed to load {module_type} {example_name}/{py_file.name}: {e}")