django-components/src/django_components/app_settings.py
Juro Oravec c692b7a310
refactor: Fix #1277 + Cache components' JS/CSS scripts at class creation (#1283)
* refactor: Cache components' JS and CSS scripts at class creation time

* refactor: add test for no template_rendered signal for component with no template
2025-07-03 12:27:21 +02:00

932 lines
31 KiB
Python

import re
from dataclasses import dataclass
from enum import Enum
from importlib import import_module
from os import PathLike
from pathlib import Path
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Generic,
List,
Literal,
NamedTuple,
Optional,
Sequence,
Tuple,
Type,
TypeVar,
Union,
cast,
)
from django.conf import settings
from django_components.util.misc import default
if TYPE_CHECKING:
from django_components.extension import ComponentExtension
from django_components.tag_formatter import TagFormatterABC
T = TypeVar("T")
ContextBehaviorType = Literal["django", "isolated"]
class ContextBehavior(str, Enum):
"""
Configure how (and whether) the context is passed to the component fills
and what variables are available inside the [`{% fill %}`](../template_tags#fill) tags.
Also see [Component context and scope](../../concepts/fundamentals/component_context_scope#context-behavior).
**Options:**
- `django`: With this setting, component fills behave as usual Django tags.
- `isolated`: This setting makes the component fills behave similar to Vue or React.
"""
DJANGO = "django"
"""
With this setting, component fills behave as usual Django tags.
That is, they enrich the context, and pass it along.
1. Component fills use the context of the component they are within.
2. Variables from [`Component.get_template_data()`](../api#django_components.Component.get_template_data)
are available to the component fill.
**Example:**
Given this template
```django
{% with cheese="feta" %}
{% component 'my_comp' %}
{{ my_var }} # my_var
{{ cheese }} # cheese
{% endcomponent %}
{% endwith %}
```
and this context returned from the `Component.get_template_data()` method
```python
{ "my_var": 123 }
```
Then if component "my_comp" defines context
```python
{ "my_var": 456 }
```
Then this will render:
```django
456 # my_var
feta # cheese
```
Because "my_comp" overrides the variable "my_var",
so `{{ my_var }}` equals `456`.
And variable "cheese" will equal `feta`, because the fill CAN access
the current context.
"""
ISOLATED = "isolated"
"""
This setting makes the component fills behave similar to Vue or React, where
the fills use EXCLUSIVELY the context variables defined in
[`Component.get_template_data()`](../api#django_components.Component.get_template_data).
**Example:**
Given this template
```django
{% with cheese="feta" %}
{% component 'my_comp' %}
{{ my_var }} # my_var
{{ cheese }} # cheese
{% endcomponent %}
{% endwith %}
```
and this context returned from the `get_template_data()` method
```python
{ "my_var": 123 }
```
Then if component "my_comp" defines context
```python
{ "my_var": 456 }
```
Then this will render:
```django
123 # my_var
# cheese
```
Because both variables "my_var" and "cheese" are taken from the root context.
Since "cheese" is not defined in root context, it's empty.
"""
# This is the source of truth for the settings that are available. If the documentation
# or the defaults do NOT match this, they should be updated.
class ComponentsSettings(NamedTuple):
"""
Settings available for django_components.
**Example:**
```python
COMPONENTS = ComponentsSettings(
autodiscover=False,
dirs = [BASE_DIR / "components"],
)
```
"""
extensions: Optional[Sequence[Union[Type["ComponentExtension"], str]]] = None
"""
List of [extensions](../../concepts/advanced/extensions) to be loaded.
The extensions can be specified as:
- Python import path, e.g. `"path.to.my_extension.MyExtension"`.
- Extension class, e.g. `my_extension.MyExtension`.
Read more about [extensions](../../concepts/advanced/extensions).
**Example:**
```python
COMPONENTS = ComponentsSettings(
extensions=[
"path.to.my_extension.MyExtension",
StorybookExtension,
],
)
```
"""
extensions_defaults: Optional[Dict[str, Any]] = None
"""
Global defaults for the extension classes.
Read more about [Extension defaults](../../concepts/advanced/extensions#extension-defaults).
**Example:**
```python
COMPONENTS = ComponentsSettings(
extensions_defaults={
"my_extension": {
"my_setting": "my_value",
},
"cache": {
"enabled": True,
"ttl": 60,
},
},
)
```
"""
autodiscover: Optional[bool] = None
"""
Toggle whether to run [autodiscovery](../../concepts/fundamentals/autodiscovery) at the Django server startup.
Defaults to `True`
```python
COMPONENTS = ComponentsSettings(
autodiscover=False,
)
```
"""
dirs: Optional[Sequence[Union[str, PathLike, Tuple[str, str], Tuple[str, PathLike]]]] = None
"""
Specify the directories that contain your components.
Defaults to `[Path(settings.BASE_DIR) / "components"]`. That is, the root `components/` app.
Directories must be full paths, same as with
[STATICFILES_DIRS](https://docs.djangoproject.com/en/5.2/ref/settings/#std-setting-STATICFILES_DIRS).
These locations are searched during [autodiscovery](../../concepts/fundamentals/autodiscovery),
or when you [define HTML, JS, or CSS as separate files](../../concepts/fundamentals/defining_js_css_html_files).
```python
COMPONENTS = ComponentsSettings(
dirs=[BASE_DIR / "components"],
)
```
Set to empty list to disable global components directories:
```python
COMPONENTS = ComponentsSettings(
dirs=[],
)
```
"""
app_dirs: Optional[Sequence[str]] = None
"""
Specify the app-level directories that contain your components.
Defaults to `["components"]`. That is, for each Django app, we search `<app>/components/` for components.
The paths must be relative to app, e.g.:
```python
COMPONENTS = ComponentsSettings(
app_dirs=["my_comps"],
)
```
To search for `<app>/my_comps/`.
These locations are searched during [autodiscovery](../../concepts/fundamentals/autodiscovery),
or when you [define HTML, JS, or CSS as separate files](../../concepts/fundamentals/defining_js_css_html_files).
Set to empty list to disable app-level components:
```python
COMPONENTS = ComponentsSettings(
app_dirs=[],
)
```
"""
cache: Optional[str] = None
"""
Name of the [Django cache](https://docs.djangoproject.com/en/5.2/topics/cache/)
to be used for storing component's JS and CSS files.
If `None`, a [`LocMemCache`](https://docs.djangoproject.com/en/5.2/topics/cache/#local-memory-caching)
is used with default settings.
Defaults to `None`.
Read more about [caching](../../guides/setup/caching).
```python
COMPONENTS = ComponentsSettings(
cache="my_cache",
)
```
"""
context_behavior: Optional[ContextBehaviorType] = None
"""
Configure whether, inside a component template, you can use variables from the outside
([`"django"`](../api#django_components.ContextBehavior.DJANGO))
or not ([`"isolated"`](../api#django_components.ContextBehavior.ISOLATED)).
This also affects what variables are available inside the [`{% fill %}`](../template_tags#fill)
tags.
Also see [Component context and scope](../../concepts/fundamentals/component_context_scope#context-behavior).
Defaults to `"django"`.
```python
COMPONENTS = ComponentsSettings(
context_behavior="isolated",
)
```
> NOTE: `context_behavior` and `slot_context_behavior` options were merged in v0.70.
>
> If you are migrating from BEFORE v0.67, set `context_behavior` to `"django"`.
> From v0.67 to v0.78 (incl) the default value was `"isolated"`.
>
> For v0.79 and later, the default is again `"django"`. See the rationale for change
> [here](https://github.com/django-components/django-components/issues/498).
"""
# TODO_v1 - remove. Users should use extension defaults instead.
debug_highlight_components: Optional[bool] = None
"""
DEPRECATED. Use
[`extensions_defaults`](../settings/#django_components.app_settings.ComponentsSettings.extensions_defaults)
instead. Will be removed in v1.
Enable / disable component highlighting.
See [Troubleshooting](../../guides/other/troubleshooting#component-highlighting) for more details.
Defaults to `False`.
```python
COMPONENTS = ComponentsSettings(
debug_highlight_components=True,
)
```
"""
# TODO_v1 - remove. Users should use extension defaults instead.
debug_highlight_slots: Optional[bool] = None
"""
DEPRECATED. Use
[`extensions_defaults`](../settings/#django_components.app_settings.ComponentsSettings.extensions_defaults)
instead. Will be removed in v1.
Enable / disable slot highlighting.
See [Troubleshooting](../../guides/other/troubleshooting#slot-highlighting) for more details.
Defaults to `False`.
```python
COMPONENTS = ComponentsSettings(
debug_highlight_slots=True,
)
```
"""
dynamic_component_name: Optional[str] = None
"""
By default, the [dynamic component](../components#django_components.components.dynamic.DynamicComponent)
is registered under the name `"dynamic"`.
In case of a conflict, you can use this setting to change the component name used for
the dynamic components.
```python
# settings.py
COMPONENTS = ComponentsSettings(
dynamic_component_name="my_dynamic",
)
```
After which you will be able to use the dynamic component with the new name:
```django
{% component "my_dynamic" is=table_comp data=table_data headers=table_headers %}
{% fill "pagination" %}
{% component "pagination" / %}
{% endfill %}
{% endcomponent %}
```
"""
libraries: Optional[List[str]] = None
"""
Configure extra python modules that should be loaded.
This may be useful if you are not using the [autodiscovery feature](../../concepts/fundamentals/autodiscovery),
or you need to load components from non-standard locations. Thus you can have
a structure of components that is independent from your apps.
Expects a list of python module paths. Defaults to empty list.
**Example:**
```python
COMPONENTS = ComponentsSettings(
libraries=[
"mysite.components.forms",
"mysite.components.buttons",
"mysite.components.cards",
],
)
```
This would be the equivalent of importing these modules from within Django's
[`AppConfig.ready()`](https://docs.djangoproject.com/en/5.2/ref/applications/#django.apps.AppConfig.ready):
```python
class MyAppConfig(AppConfig):
def ready(self):
import "mysite.components.forms"
import "mysite.components.buttons"
import "mysite.components.cards"
```
# Manually loading libraries
In the rare case that you need to manually trigger the import of libraries, you can use
the [`import_libraries()`](../api/#django_components.import_libraries) function:
```python
from django_components import import_libraries
import_libraries()
```
"""
multiline_tags: Optional[bool] = None
"""
Enable / disable
[multiline support for template tags](../../concepts/fundamentals/template_tag_syntax#multiline-tags).
If `True`, template tags like `{% component %}` or `{{ my_var }}` can span multiple lines.
Defaults to `True`.
Disable this setting if you are making custom modifications to Django's
regular expression for parsing templates at `django.template.base.tag_re`.
```python
COMPONENTS = ComponentsSettings(
multiline_tags=False,
)
```
"""
# TODO_REMOVE_IN_V1
reload_on_template_change: Optional[bool] = None
"""Deprecated. Use
[`COMPONENTS.reload_on_file_change`](../settings/#django_components.app_settings.ComponentsSettings.reload_on_file_change)
instead.""" # noqa: E501
reload_on_file_change: Optional[bool] = None
"""
This is relevant if you are using the project structure where
HTML, JS, CSS and Python are in separate files and nested in a directory.
In this case you may notice that when you are running a development server,
the server sometimes does not reload when you change component files.
Django's native [live reload](https://stackoverflow.com/a/66023029/9788634) logic
handles only Python files and HTML template files. It does NOT reload when other
file types change or when template files are nested more than one level deep.
The setting `reload_on_file_change` fixes this, reloading the dev server even when your component's
HTML, JS, or CSS changes.
If `True`, django_components configures Django to reload when files inside
[`COMPONENTS.dirs`](../settings/#django_components.app_settings.ComponentsSettings.dirs)
or
[`COMPONENTS.app_dirs`](../settings/#django_components.app_settings.ComponentsSettings.app_dirs)
change.
See [Reload dev server on component file changes](../../guides/setup/development_server/#reload-dev-server-on-component-file-changes).
Defaults to `False`.
!!! warning
This setting should be enabled only for the dev environment!
""" # noqa: E501
static_files_allowed: Optional[List[Union[str, re.Pattern]]] = None
"""
A list of file extensions (including the leading dot) that define which files within
[`COMPONENTS.dirs`](../settings/#django_components.app_settings.ComponentsSettings.dirs)
or
[`COMPONENTS.app_dirs`](../settings/#django_components.app_settings.ComponentsSettings.app_dirs)
are treated as [static files](https://docs.djangoproject.com/en/5.2/howto/static-files/).
If a file is matched against any of the patterns, it's considered a static file. Such files are collected
when running [`collectstatic`](https://docs.djangoproject.com/en/5.2/ref/contrib/staticfiles/#collectstatic),
and can be accessed under the
[static file endpoint](https://docs.djangoproject.com/en/5.2/ref/settings/#static-url).
You can also pass in compiled regexes ([`re.Pattern`](https://docs.python.org/3/library/re.html#re.Pattern))
for more advanced patterns.
By default, JS, CSS, and common image and font file formats are considered static files:
```python
COMPONENTS = ComponentsSettings(
static_files_allowed=[
".css",
".js", ".jsx", ".ts", ".tsx",
# Images
".apng", ".png", ".avif", ".gif", ".jpg",
".jpeg", ".jfif", ".pjpeg", ".pjp", ".svg",
".webp", ".bmp", ".ico", ".cur", ".tif", ".tiff",
# Fonts
".eot", ".ttf", ".woff", ".otf", ".svg",
],
)
```
!!! warning
Exposing your Python files can be a security vulnerability.
See [Security notes](../../overview/security_notes).
"""
# TODO_REMOVE_IN_V1
forbidden_static_files: Optional[List[Union[str, re.Pattern]]] = None
"""Deprecated. Use
[`COMPONENTS.static_files_forbidden`](../settings/#django_components.app_settings.ComponentsSettings.static_files_forbidden)
instead.""" # noqa: E501
static_files_forbidden: Optional[List[Union[str, re.Pattern]]] = None
"""
A list of file extensions (including the leading dot) that define which files within
[`COMPONENTS.dirs`](../settings/#django_components.app_settings.ComponentsSettings.dirs)
or
[`COMPONENTS.app_dirs`](../settings/#django_components.app_settings.ComponentsSettings.app_dirs)
will NEVER be treated as [static files](https://docs.djangoproject.com/en/5.2/howto/static-files/).
If a file is matched against any of the patterns, it will never be considered a static file,
even if the file matches a pattern in
[`static_files_allowed`](../settings/#django_components.app_settings.ComponentsSettings.static_files_allowed).
Use this setting together with
[`static_files_allowed`](../settings/#django_components.app_settings.ComponentsSettings.static_files_allowed)
for a fine control over what file types will be exposed.
You can also pass in compiled regexes ([`re.Pattern`](https://docs.python.org/3/library/re.html#re.Pattern))
for more advanced patterns.
By default, any HTML and Python are considered NOT static files:
```python
COMPONENTS = ComponentsSettings(
static_files_forbidden=[
".html", ".django", ".dj", ".tpl",
# Python files
".py", ".pyc",
],
)
```
!!! warning
Exposing your Python files can be a security vulnerability.
See [Security notes](../../overview/security_notes).
"""
tag_formatter: Optional[Union["TagFormatterABC", str]] = None
"""
Configure what syntax is used inside Django templates to render components.
See the [available tag formatters](../tag_formatters).
Defaults to `"django_components.component_formatter"`.
Learn more about [Customizing component tags with TagFormatter](../../concepts/advanced/tag_formatter).
Can be set either as direct reference:
```python
from django_components import component_formatter
COMPONENTS = ComponentsSettings(
"tag_formatter": component_formatter
)
```
Or as an import string;
```python
COMPONENTS = ComponentsSettings(
"tag_formatter": "django_components.component_formatter"
)
```
**Examples:**
- `"django_components.component_formatter"`
Set
```python
COMPONENTS = ComponentsSettings(
"tag_formatter": "django_components.component_formatter"
)
```
To write components like this:
```django
{% component "button" href="..." %}
Click me!
{% endcomponent %}
```
- `django_components.component_shorthand_formatter`
Set
```python
COMPONENTS = ComponentsSettings(
"tag_formatter": "django_components.component_shorthand_formatter"
)
```
To write components like this:
```django
{% button href="..." %}
Click me!
{% endbutton %}
```
"""
# TODO_V1 - remove
template_cache_size: Optional[int] = None
"""
DEPRECATED. Template caching will be removed in v1.
Configure the maximum amount of Django templates to be cached.
Defaults to `128`.
Each time a [Django template](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Template)
is rendered, it is cached to a global in-memory cache (using Python's
[`lru_cache`](https://docs.python.org/3/library/functools.html#functools.lru_cache)
decorator). This speeds up the next render of the component.
As the same component is often used many times on the same page, these savings add up.
By default the cache holds 128 component templates in memory, which should be enough for most sites.
But if you have a lot of components, or if you are overriding
[`Component.get_template()`](../api#django_components.Component.get_template)
to render many dynamic templates, you can increase this number.
```python
COMPONENTS = ComponentsSettings(
template_cache_size=256,
)
```
To remove the cache limit altogether and cache everything, set `template_cache_size` to `None`.
```python
COMPONENTS = ComponentsSettings(
template_cache_size=None,
)
```
If you want to add templates to the cache yourself, you can use
[`cached_template()`](../api/#django_components.cached_template):
```python
from django_components import cached_template
cached_template("Variable: {{ variable }}")
# You can optionally specify Template class, and other Template inputs:
class MyTemplate(Template):
pass
cached_template(
"Variable: {{ variable }}",
template_cls=MyTemplate,
name=...
origin=...
engine=...
)
```
"""
# NOTE: Some defaults depend on the Django settings, which may not yet be
# initialized at the time that these settings are generated. For such cases
# we define the defaults as a factory function, and use the `Dynamic` class to
# mark such fields.
@dataclass(frozen=True)
class Dynamic(Generic[T]):
getter: Callable[[], T]
# This is the source of truth for the settings defaults. If the documentation
# does NOT match it, the documentation should be updated.
#
# NOTE: Because we need to access Django settings to generate default dirs
# for `COMPONENTS.dirs`, we do it lazily.
# NOTE 2: We show the defaults in the documentation, together with the comments
# (except for the `Dynamic` instances and comments like `type: ignore`).
# So `fmt: off` turns off Black formatting and `snippet:defaults` allows
# us to extract the snippet from the file.
#
# fmt: off
# --snippet:defaults--
defaults = ComponentsSettings(
autodiscover=True,
cache=None,
context_behavior=ContextBehavior.DJANGO.value, # "django" | "isolated"
# Root-level "components" dirs, e.g. `/path/to/proj/components/`
dirs=Dynamic(lambda: [Path(settings.BASE_DIR) / "components"]), # type: ignore[arg-type]
# App-level "components" dirs, e.g. `[app]/components/`
app_dirs=["components"],
debug_highlight_components=False,
debug_highlight_slots=False,
dynamic_component_name="dynamic",
extensions=[],
extensions_defaults={},
libraries=[], # E.g. ["mysite.components.forms", ...]
multiline_tags=True,
reload_on_file_change=False,
static_files_allowed=[
".css",
".js", ".jsx", ".ts", ".tsx",
# Images
".apng", ".png", ".avif", ".gif", ".jpg",
".jpeg", ".jfif", ".pjpeg", ".pjp", ".svg",
".webp", ".bmp", ".ico", ".cur", ".tif", ".tiff",
# Fonts
".eot", ".ttf", ".woff", ".otf", ".svg",
],
static_files_forbidden=[
# See https://marketplace.visualstudio.com/items?itemName=junstyle.vscode-django-support
".html", ".django", ".dj", ".tpl",
# Python files
".py", ".pyc",
],
tag_formatter="django_components.component_formatter",
template_cache_size=128,
)
# --endsnippet:defaults--
# fmt: on
# Interface through which we access the settings.
#
# This is the only place where we actually access the settings.
# The settings are merged with defaults, and then validated.
#
# The settings are then available through the `app_settings` object.
#
# Settings are loaded from Django settings only once, at `apps.py` in `ready()`.
class InternalSettings:
def __init__(self) -> None:
self._settings: Optional[ComponentsSettings] = None
def _load_settings(self) -> None:
data = getattr(settings, "COMPONENTS", {})
components_settings = ComponentsSettings(**data) if not isinstance(data, ComponentsSettings) else data
# Merge we defaults and otherwise initialize if necessary
# For DIRS setting, we use a getter for the default value, because the default value
# uses Django settings, which may not yet be initialized at the time these settings are generated.
dirs_default_fn = cast(Dynamic[Sequence[Union[str, Tuple[str, str]]]], defaults.dirs)
dirs_default = dirs_default_fn.getter()
self._settings = ComponentsSettings(
autodiscover=default(components_settings.autodiscover, defaults.autodiscover),
cache=default(components_settings.cache, defaults.cache),
dirs=default(components_settings.dirs, dirs_default),
app_dirs=default(components_settings.app_dirs, defaults.app_dirs),
debug_highlight_components=default(
components_settings.debug_highlight_components, defaults.debug_highlight_components
),
debug_highlight_slots=default(components_settings.debug_highlight_slots, defaults.debug_highlight_slots),
dynamic_component_name=default(
components_settings.dynamic_component_name, defaults.dynamic_component_name
),
libraries=default(components_settings.libraries, defaults.libraries),
# NOTE: Internally we store the extensions as a list of instances, but the user
# can pass in either a list of classes or a list of import strings.
extensions=self._prepare_extensions(components_settings), # type: ignore[arg-type]
extensions_defaults=default(components_settings.extensions_defaults, defaults.extensions_defaults),
multiline_tags=default(components_settings.multiline_tags, defaults.multiline_tags),
reload_on_file_change=self._prepare_reload_on_file_change(components_settings),
template_cache_size=default(components_settings.template_cache_size, defaults.template_cache_size),
static_files_allowed=default(components_settings.static_files_allowed, defaults.static_files_allowed),
static_files_forbidden=self._prepare_static_files_forbidden(components_settings),
context_behavior=self._prepare_context_behavior(components_settings),
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)
)
# Prepend built-in extensions
from django_components.extensions.cache import CacheExtension
from django_components.extensions.debug_highlight import DebugHighlightExtension
from django_components.extensions.defaults import DefaultsExtension
from django_components.extensions.dependencies import DependenciesExtension
from django_components.extensions.view import ViewExtension
extensions = cast(
List[Type["ComponentExtension"]],
[
CacheExtension,
DefaultsExtension,
DependenciesExtension,
ViewExtension,
DebugHighlightExtension,
],
) + list(extensions)
# Extensions may be passed in either as classes or import strings.
extension_instances: List["ComponentExtension"] = []
for extension in extensions:
if isinstance(extension, str):
import_path, class_name = extension.rsplit(".", 1)
extension_module = import_module(import_path)
extension = cast(Type["ComponentExtension"], getattr(extension_module, class_name))
if isinstance(extension, type):
extension_instance = extension()
else:
extension_instances.append(extension)
extension_instances.append(extension_instance)
return extension_instances
def _prepare_reload_on_file_change(self, new_settings: ComponentsSettings) -> bool:
val = new_settings.reload_on_file_change
# TODO_REMOVE_IN_V1
if val is None:
val = new_settings.reload_on_template_change
return default(val, cast(bool, defaults.reload_on_file_change))
def _prepare_static_files_forbidden(self, new_settings: ComponentsSettings) -> List[Union[str, re.Pattern]]:
val = new_settings.static_files_forbidden
# TODO_REMOVE_IN_V1
if val is None:
val = new_settings.forbidden_static_files
return default(val, cast(List[Union[str, re.Pattern]], defaults.static_files_forbidden))
def _prepare_context_behavior(self, new_settings: ComponentsSettings) -> Literal["django", "isolated"]:
raw_value = cast(
Literal["django", "isolated"],
default(new_settings.context_behavior, defaults.context_behavior),
)
try:
ContextBehavior(raw_value)
except ValueError:
valid_values = [behavior.value for behavior in ContextBehavior]
raise ValueError(f"Invalid context behavior: {raw_value}. Valid options are {valid_values}")
return raw_value
@property
def AUTODISCOVER(self) -> bool:
return self._get_settings().autodiscover # type: ignore[return-value]
@property
def CACHE(self) -> Optional[str]:
return self._get_settings().cache
@property
def DIRS(self) -> Sequence[Union[str, PathLike, Tuple[str, str], Tuple[str, PathLike]]]:
return self._get_settings().dirs # type: ignore[return-value]
@property
def APP_DIRS(self) -> Sequence[str]:
return self._get_settings().app_dirs # type: ignore[return-value]
@property
def DEBUG_HIGHLIGHT_COMPONENTS(self) -> bool:
return self._get_settings().debug_highlight_components # type: ignore[return-value]
@property
def DEBUG_HIGHLIGHT_SLOTS(self) -> bool:
return self._get_settings().debug_highlight_slots # type: ignore[return-value]
@property
def DYNAMIC_COMPONENT_NAME(self) -> str:
return self._get_settings().dynamic_component_name # type: ignore[return-value]
@property
def LIBRARIES(self) -> List[str]:
return self._get_settings().libraries # type: ignore[return-value]
@property
def EXTENSIONS(self) -> List["ComponentExtension"]:
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._get_settings().multiline_tags # type: ignore[return-value]
@property
def RELOAD_ON_FILE_CHANGE(self) -> bool:
return self._get_settings().reload_on_file_change # type: ignore[return-value]
@property
def TEMPLATE_CACHE_SIZE(self) -> int:
return self._get_settings().template_cache_size # type: ignore[return-value]
@property
def STATIC_FILES_ALLOWED(self) -> Sequence[Union[str, re.Pattern]]:
return self._get_settings().static_files_allowed # type: ignore[return-value]
@property
def STATIC_FILES_FORBIDDEN(self) -> Sequence[Union[str, re.Pattern]]:
return self._get_settings().static_files_forbidden # type: ignore[return-value]
@property
def CONTEXT_BEHAVIOR(self) -> ContextBehavior:
return ContextBehavior(self._get_settings().context_behavior)
@property
def TAG_FORMATTER(self) -> Union["TagFormatterABC", str]:
return self._get_settings().tag_formatter # type: ignore[return-value]
app_settings = InternalSettings()