diff --git a/src/django_components/__init__.py b/src/django_components/__init__.py index 12a573c8..5f42192f 100644 --- a/src/django_components/__init__.py +++ b/src/django_components/__init__.py @@ -1,8 +1,7 @@ import importlib import importlib.util -import sys +import os from pathlib import Path -from typing import Union import django from django.utils.module_loading import autodiscover_modules @@ -21,7 +20,7 @@ def autodiscover() -> None: autodiscover_modules("components") # Autodetect a .py file in a components dir - component_filepaths = search(search_glob="**/*.py") + component_filepaths = search(search_glob="**/*.py").matched_files for path in component_filepaths: import_file(path) @@ -29,12 +28,20 @@ def autodiscover() -> None: importlib.import_module(path_lib) -def import_file(path: Union[str, Path]) -> None: - MODULE_PATH = path - MODULE_NAME = Path(path).stem - spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH) - if spec is None: - raise ValueError(f"Cannot import file '{path}' - invalid path") - module = importlib.util.module_from_spec(spec) - sys.modules[spec.name] = module - spec.loader.exec_module(module) # type: ignore +def import_file(file_path: Path) -> None: + # Get the root dir, see https://stackoverflow.com/a/16413955/9788634 + project_root = os.path.abspath(os.path.dirname(__name__)) + + # Derive python import path from the filesystem path + # Example: + # If project root is `/path/to/project` + # And file_path is `/path/to/project/app/components/mycomp.py` + # Then the path relative to project root is `app/components/mycomp.py` + # Which we then turn into python import path `app.components.mycomp` + rel_path = os.path.relpath(file_path, start=project_root) + rel_path_without_suffix = str(Path(rel_path).with_suffix("")) + module_name = rel_path_without_suffix.replace(os.sep, ".") + + # This imports the file and runs it's code. So if the file defines any + # django components, they will be registered. + importlib.import_module(module_name) diff --git a/src/django_components/component.py b/src/django_components/component.py index 17348cfa..fdcc80df 100644 --- a/src/django_components/component.py +++ b/src/django_components/component.py @@ -89,7 +89,7 @@ def _resolve_component_relative_files(attrs: MutableMapping) -> None: # Prepare all possible directories we need to check when searching for # component's template and media files - components_dirs = search() + components_dirs = search().searched_dirs # Get the directory where the component class is defined try: diff --git a/src/django_components/utils.py b/src/django_components/utils.py index 49ad9b7f..13e5f018 100644 --- a/src/django_components/utils.py +++ b/src/django_components/utils.py @@ -1,13 +1,18 @@ import glob from pathlib import Path -from typing import List, Optional, Union +from typing import List, Optional, NamedTuple from django.template.engine import Engine from django_components.template_loader import Loader -def search(search_glob: Optional[str] = None, engine: Optional[Engine] = None) -> Union[List[str], List[Path]]: +class SearchResult(NamedTuple): + searched_dirs: List[Path] + matched_files: List[Path] + + +def search(search_glob: Optional[str] = None, engine: Optional[Engine] = None) -> SearchResult: """ Search for directories that may contain components. @@ -22,11 +27,11 @@ def search(search_glob: Optional[str] = None, engine: Optional[Engine] = None) - dirs = loader.get_dirs() if search_glob is None: - return dirs + return SearchResult(searched_dirs=dirs, matched_files=[]) - component_filenames: List[str] = [] + component_filenames: List[Path] = [] for directory in dirs: for path in glob.iglob(str(Path(directory) / search_glob), recursive=True): - component_filenames.append(path) + component_filenames.append(Path(path)) - return component_filenames + return SearchResult(searched_dirs=dirs, matched_files=component_filenames)