diff --git a/CHANGELOG.md b/CHANGELOG.md index 76ec4614..10c3e6d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,10 @@ Now, each subclass has it's own `Template` instance, and changes to the template of the subclass do not affect the template of the parent class. +- Fix Django failing to restart due to "TypeError: 'Dynamic' object is not iterable" ([#1232](https://github.com/django-components/django-components/issues/1232)) + +- Fix bug when error formatting failed when error value was not a string. + ## v0.140.1 #### Fix diff --git a/docs/reference/.nav.yml b/docs/reference/.nav.yml index dbea3cb1..5f107924 100644 --- a/docs/reference/.nav.yml +++ b/docs/reference/.nav.yml @@ -1,16 +1,16 @@ # `.nav.yml` is provided by https://lukasgeiter.github.io/mkdocs-awesome-nav nav: - API: api.md - - Commands: commands.md + - CLI commands: commands.md - Components: components.md - Exceptions: exceptions.md - - Extension commands: extension_commands.md - Extension hooks: extension_hooks.md - - Extension URLs: extension_urls.md + - Extension commands API: extension_commands.md + - Extension URLs API: extension_urls.md - Settings: settings.md - Signals: signals.md - Tag formatters: tag_formatters.md - Template tags: template_tags.md - - Template vars: template_vars.md + - Template variables: template_vars.md - URLs: urls.md - Testing API: testing_api.md diff --git a/docs/reference/extension_commands.md b/docs/reference/extension_commands.md index ba29d401..40a18b83 100644 --- a/docs/reference/extension_commands.md +++ b/docs/reference/extension_commands.md @@ -1,6 +1,6 @@ -# Extension commands +# Extension commands API Overview of all classes, functions, and other objects related to defining extension commands. diff --git a/docs/reference/extension_urls.md b/docs/reference/extension_urls.md index f651ceb7..10570d2e 100644 --- a/docs/reference/extension_urls.md +++ b/docs/reference/extension_urls.md @@ -1,6 +1,6 @@ -# Extension URLs +# Extension URLs API Overview of all classes, functions, and other objects related to defining extension URLs. diff --git a/docs/reference/template_tags.md b/docs/reference/template_tags.md index b0fd167b..d5c9145f 100644 --- a/docs/reference/template_tags.md +++ b/docs/reference/template_tags.md @@ -67,7 +67,7 @@ If you insert this tag multiple times, ALL JS scripts will be duplicately insert -See source code +See source code diff --git a/docs/reference/template_vars.md b/docs/reference/template_variables.md similarity index 100% rename from docs/reference/template_vars.md rename to docs/reference/template_variables.md diff --git a/docs/scripts/reference.py b/docs/scripts/reference.py index 6f1ee7da..28963a22 100644 --- a/docs/scripts/reference.py +++ b/docs/scripts/reference.py @@ -44,6 +44,7 @@ from pathlib import Path from textwrap import dedent from typing import Any, Dict, List, NamedTuple, Optional, Sequence, Tuple, Type, Union +from django.conf import settings from django.core.management.base import BaseCommand from django.urls import URLPattern, URLResolver @@ -465,7 +466,13 @@ def gen_reference_commands(): # Add link to source code module_abs_path = import_module(cmd_def_cls.__module__).__file__ module_rel_path = Path(module_abs_path).relative_to(Path.cwd()).as_posix() # type: ignore[arg-type] - obj_lineno = inspect.findsource(cmd_def_cls)[1] + + # NOTE: Raises `OSError` if the file is not found. + try: + obj_lineno = inspect.findsource(cmd_def_cls)[1] + except Exception: + obj_lineno = None + source_code_link = _format_source_code_html(module_rel_path, obj_lineno) # NOTE: For the commands we have to generate the markdown entries ourselves, @@ -524,7 +531,7 @@ def gen_reference_commands(): ) -def gen_reference_templatetags(): +def gen_reference_template_tags(): """ Generate documentation for all Django template tags defined by django-components, like `{% slot %}`, `{% component %}`, etc. @@ -537,7 +544,7 @@ def gen_reference_templatetags(): ] preface = "\n\n" - preface += (root / "docs/templates/reference_templatetags.md").read_text() + preface += (root / "docs/templates/reference_template_tags.md").read_text() out_file = root / "docs/reference/template_tags.md" out_file.parent.mkdir(parents=True, exist_ok=True) @@ -585,14 +592,14 @@ def gen_reference_templatetags(): ) -def gen_reference_templatevars(): +def gen_reference_template_variables(): """ Generate documentation for all variables that are available inside the component templates under the `{{ component_vars }}` variable, as defined by `ComponentVars`. """ preface = "\n\n" - preface += (root / "docs/templates/reference_templatevars.md").read_text() - out_file = root / "docs/reference/template_vars.md" + preface += (root / "docs/templates/reference_template_variables.md").read_text() + out_file = root / "docs/reference/template_variables.md" out_file.parent.mkdir(parents=True, exist_ok=True) with out_file.open("w", encoding="utf-8") as f: @@ -1099,6 +1106,13 @@ def _is_extension_url_api(obj: Any) -> bool: def gen_reference(): """The entrypoint to generate all the reference documentation.""" + + # Set up Django settings so we can import `extensions` + if not settings.configured: + settings.configure( + BASE_DIR=Path(__file__).parent.parent.parent, + ) + gen_reference_api() gen_reference_exceptions() gen_reference_components() @@ -1106,8 +1120,8 @@ def gen_reference(): gen_reference_tagformatters() gen_reference_urls() gen_reference_commands() - gen_reference_templatetags() - gen_reference_templatevars() + gen_reference_template_tags() + gen_reference_template_variables() gen_reference_signals() gen_reference_testing_api() gen_reference_extension_hooks() diff --git a/docs/templates/reference_extension_commands.md b/docs/templates/reference_extension_commands.md index 0df90645..9d9c51ea 100644 --- a/docs/templates/reference_extension_commands.md +++ b/docs/templates/reference_extension_commands.md @@ -1,4 +1,4 @@ -# Extension commands +# Extension commands API Overview of all classes, functions, and other objects related to defining extension commands. diff --git a/docs/templates/reference_extension_urls.md b/docs/templates/reference_extension_urls.md index 10692b23..eaa55135 100644 --- a/docs/templates/reference_extension_urls.md +++ b/docs/templates/reference_extension_urls.md @@ -1,4 +1,4 @@ -# Extension URLs +# Extension URLs API Overview of all classes, functions, and other objects related to defining extension URLs. diff --git a/docs/templates/reference_templatetags.md b/docs/templates/reference_template_tags.md similarity index 100% rename from docs/templates/reference_templatetags.md rename to docs/templates/reference_template_tags.md diff --git a/docs/templates/reference_templatevars.md b/docs/templates/reference_template_variables.md similarity index 100% rename from docs/templates/reference_templatevars.md rename to docs/templates/reference_template_variables.md diff --git a/src/django_components/app_settings.py b/src/django_components/app_settings.py index 53bfacc8..ab26519a 100644 --- a/src/django_components/app_settings.py +++ b/src/django_components/app_settings.py @@ -746,8 +746,8 @@ defaults = ComponentsSettings( # # Settings are loaded from Django settings only once, at `apps.py` in `ready()`. class InternalSettings: - def __init__(self, settings: Optional[Dict[str, Any]] = None): - self._settings = ComponentsSettings(**settings) if settings else defaults + def __init__(self) -> None: + self._settings: Optional[ComponentsSettings] = None def _load_settings(self) -> None: data = getattr(settings, "COMPONENTS", {}) @@ -786,6 +786,11 @@ class InternalSettings: tag_formatter=default(components_settings.tag_formatter, defaults.tag_formatter), # type: ignore[arg-type] ) + def _get_settings(self) -> ComponentsSettings: + if self._settings is None: + self._load_settings() + return cast(ComponentsSettings, self._settings) + def _prepare_extensions(self, new_settings: ComponentsSettings) -> List["ComponentExtension"]: extensions: Sequence[Union[Type["ComponentExtension"], str]] = default( new_settings.extensions, cast(List[str], defaults.extensions) @@ -853,70 +858,73 @@ class InternalSettings: return raw_value - # TODO REMOVE THE PROPERTIES BELOW? THEY NO LONGER SERVE ANY PURPOSE @property def AUTODISCOVER(self) -> bool: - return self._settings.autodiscover # type: ignore[return-value] + return self._get_settings().autodiscover # type: ignore[return-value] @property def CACHE(self) -> Optional[str]: - return self._settings.cache + return self._get_settings().cache @property def DIRS(self) -> Sequence[Union[str, PathLike, Tuple[str, str], Tuple[str, PathLike]]]: - return self._settings.dirs # type: ignore[return-value] + return self._get_settings().dirs # type: ignore[return-value] @property def APP_DIRS(self) -> Sequence[str]: - return self._settings.app_dirs # type: ignore[return-value] + return self._get_settings().app_dirs # type: ignore[return-value] @property def DEBUG_HIGHLIGHT_COMPONENTS(self) -> bool: - return self._settings.debug_highlight_components # type: ignore[return-value] + return self._get_settings().debug_highlight_components # type: ignore[return-value] @property def DEBUG_HIGHLIGHT_SLOTS(self) -> bool: - return self._settings.debug_highlight_slots # type: ignore[return-value] + return self._get_settings().debug_highlight_slots # type: ignore[return-value] @property def DYNAMIC_COMPONENT_NAME(self) -> str: - return self._settings.dynamic_component_name # type: ignore[return-value] + return self._get_settings().dynamic_component_name # type: ignore[return-value] @property def LIBRARIES(self) -> List[str]: - return self._settings.libraries # type: ignore[return-value] + return self._get_settings().libraries # type: ignore[return-value] @property def EXTENSIONS(self) -> List["ComponentExtension"]: - return self._settings.extensions # type: ignore[return-value] + return self._get_settings().extensions # type: ignore[return-value] + + @property + def EXTENSIONS_DEFAULTS(self) -> Dict[str, Any]: + return self._get_settings().extensions_defaults # type: ignore[return-value] @property def MULTILINE_TAGS(self) -> bool: - return self._settings.multiline_tags # type: ignore[return-value] + return self._get_settings().multiline_tags # type: ignore[return-value] @property def RELOAD_ON_FILE_CHANGE(self) -> bool: - return self._settings.reload_on_file_change # type: ignore[return-value] + return self._get_settings().reload_on_file_change # type: ignore[return-value] @property def TEMPLATE_CACHE_SIZE(self) -> int: - return self._settings.template_cache_size # type: ignore[return-value] + return self._get_settings().template_cache_size # type: ignore[return-value] @property def STATIC_FILES_ALLOWED(self) -> Sequence[Union[str, re.Pattern]]: - return self._settings.static_files_allowed # type: ignore[return-value] + return self._get_settings().static_files_allowed # type: ignore[return-value] @property def STATIC_FILES_FORBIDDEN(self) -> Sequence[Union[str, re.Pattern]]: - return self._settings.static_files_forbidden # type: ignore[return-value] + return self._get_settings().static_files_forbidden # type: ignore[return-value] @property def CONTEXT_BEHAVIOR(self) -> ContextBehavior: - return ContextBehavior(self._settings.context_behavior) + return ContextBehavior(self._get_settings().context_behavior) @property def TAG_FORMATTER(self) -> Union["TagFormatterABC", str]: - return self._settings.tag_formatter # type: ignore[return-value] + return self._get_settings().tag_formatter # type: ignore[return-value] app_settings = InternalSettings() diff --git a/src/django_components/apps.py b/src/django_components/apps.py index 3a5e4a91..9361f558 100644 --- a/src/django_components/apps.py +++ b/src/django_components/apps.py @@ -20,8 +20,6 @@ class ComponentsConfig(AppConfig): from django_components.extension import extensions from django_components.util.django_monkeypatch import monkeypatch_template_cls - app_settings._load_settings() - # NOTE: This monkeypatch is applied here, before Django processes any requests. # To make django-components work with django-debug-toolbar-template-profiler # See https://github.com/django-components/django-components/discussions/819 diff --git a/src/django_components/commands/ext_run.py b/src/django_components/commands/ext_run.py index af2825cf..17bda9fd 100644 --- a/src/django_components/commands/ext_run.py +++ b/src/django_components/commands/ext_run.py @@ -20,7 +20,7 @@ def _gen_subcommands() -> List[Type[ComponentCommand]]: commands: List[Type[ComponentCommand]] = [] for extension in extensions.extensions: ExtCommand = type( - "ExtCommand", + "ExtRunSubcommand_" + extension.name, (ComponentCommand,), { "name": extension.name, diff --git a/src/django_components/extension.py b/src/django_components/extension.py index 75ee2b5b..a95d9755 100644 --- a/src/django_components/extension.py +++ b/src/django_components/extension.py @@ -1047,7 +1047,7 @@ class ExtensionManager: # - `MyExtensionBase` is the base class that the extension class inherits from. bases_list = [ext_base_class] - all_extensions_defaults = app_settings._settings.extensions_defaults or {} + all_extensions_defaults = app_settings.EXTENSIONS_DEFAULTS or {} extension_defaults = all_extensions_defaults.get(extension.name, None) if extension_defaults: # Create dummy class that holds the extension defaults diff --git a/src/django_components/util/exception.py b/src/django_components/util/exception.py index b7dbb97b..dc57b7c3 100644 --- a/src/django_components/util/exception.py +++ b/src/django_components/util/exception.py @@ -25,7 +25,7 @@ def component_error_message(component_path: List[str]) -> Generator[None, None, if not components: orig_msg = str(err.args[0]) else: - orig_msg = err.args[0].split("\n", 1)[-1] + orig_msg = str(err.args[0]).split("\n", 1)[-1] else: orig_msg = str(err)