From 329a398c6154ea5d81f801652844366aa82fa368 Mon Sep 17 00:00:00 2001 From: Juro Oravec Date: Mon, 30 Dec 2024 22:03:31 +0100 Subject: [PATCH] docs: add documentation around component media (#877) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- CHANGELOG.md | 28 +- .../defining_js_css_html_files.md | 297 +++++++++++++++--- docs/css/style.css | 13 + src/django_components/__init__.py | 3 + src/django_components/component.py | 211 +++++++++++-- src/django_components/component_media.py | 132 +++++++- 6 files changed, 610 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc1f41a..eb1b766f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,37 @@ ## v0.124 +#### Feat + +- Instead of inlining the JS and CSS under `Component.js` and `Component.css`, you can move + them to their own files, and link the JS/CSS files with `Component.js_file` and `Component.css_file`. + + Even when you specify the JS/CSS with `Component.js_file` or `Component.css_file`, then you can still + access the content under `Component.js/css` - behind the scenes, the content of the JS/CSS files + will be set to `Component.js/css` upon first access. + + With this change, the role of `Component.js/css` and the JS/CSS in `Component.Media` has changed: + + - The JS/CSS defined in `Component.js/css` or `Component.js/css_file` is the "main" JS/CSS + - The JS/CSS defined in `Component.Media.js/css` are secondary or additional + + See the updated ["Getting Started" tutorial](https://EmilStenstrom.github.io/django-components/0.124/concepts/getting_started/adding_js_and_css/) + #### Refactor - The undocumented `Component.component_id` was removed. Instead, use `Component.id`. Changes: - - While `component_id` was unique every time you instantiated `Component`, The new `id` is unique + - While `component_id` was unique every time you instantiated `Component`, the new `id` is unique every time you render the component (e.g. with `Component.render()`) - - The new `id` is available only during render, so e.g. from within `get_context_data()` + - The new `id` is available only during render, so e.g. from within `get_context_data()` + +- Component's HTML / CSS / JS are now resolved and loaded lazily. That is, if you specify `template_name`, + `js_file`, `css_file`, or `Media.js/css`, the file paths will be resolved only once you: + + 1. Try to access component's HTML / CSS / JS, or + 2. Render the component. + + Read more on [Accessing component's HTML / JS / CSS](https://EmilStenstrom.github.io/django-components/0.124/concepts/fundamentals/defining_js_css_html_files/#customize-how-paths-are-rendered-into-html-tags). ## v0.123 diff --git a/docs/concepts/fundamentals/defining_js_css_html_files.md b/docs/concepts/fundamentals/defining_js_css_html_files.md index 09b067ac..2e84cc33 100644 --- a/docs/concepts/fundamentals/defining_js_css_html_files.md +++ b/docs/concepts/fundamentals/defining_js_css_html_files.md @@ -3,19 +3,60 @@ title: Defining HTML / JS / CSS files weight: 8 --- -django_component's management of files is inspired by [Django's `Media` class](https://docs.djangoproject.com/en/5.0/topics/forms/media/). +As you could have seen in [the tutorial](../../concepts/getting_started/adding_js_and_css.md), there's multiple ways how you can associate +HTML / JS / CSS with a component: -To be familiar with how Django handles static files, we recommend reading also: +- You can set [`Component.template`](../../reference/api.md#django_components.Component.template), + [`Component.css`](../../reference/api.md#django_components.Component.css) and + [`Component.js`](../../reference/api.md#django_components.Component.js) to define the main HTML / CSS / JS for a component + as inlined code. +- You can set [`Component.template_name`](../../reference/api.md#django_components.Component.template_name), + [`Component.css_file`](../../reference/api.md#django_components.Component.css_file) and + [`Component.js_file`](../../reference/api.md#django_components.Component.js_file) to define the main HTML / CSS / JS + for a component in separate files. +- You can link additional CSS / JS files using + [`Component.Media.js`](../../reference/api.md#django_components.ComponentMediaInput.js) + and [`Component.Media.css`](../../reference/api.md#django_components.ComponentMediaInput.css). -- [How to manage static files (e.g. images, JavaScript, CSS)](https://docs.djangoproject.com/en/5.0/howto/static-files/) +!!! warning -## Defining file paths relative to component or static dirs + You **cannot** use both inlined code **and** separate file for a single language type: -As seen in the [getting started example](#create-your-first-component), to associate HTML/JS/CSS -files with a component, you set them as `template_name`, `js_file` and `css_file` respectively: + - You can only either set `Component.template` or `Component.template_name` + - You can only either set `Component.css` or `Component.css_file` + - You can only either set `Component.js` or `Component.js_file` -```py -# In a file [project root]/components/calendar/calendar.py + However, you can freely mix these for different languages: + + ```py + class MyTable(Component): + template: types.django_html = """ +
+ Hi there! +
+ """ + js_file = "my_table.js" + css_file = "my_table.css" + ``` + +!!! note + + django-component's management of files is inspired by [Django's `Media` class](https://docs.djangoproject.com/en/5.0/topics/forms/media/). + + To be familiar with how Django handles static files, we recommend reading also: + + - [How to manage static files (e.g. images, JavaScript, CSS)](https://docs.djangoproject.com/en/5.0/howto/static-files/) + +## Defining file paths relative to component + +As seen in the [getting started example](../getting_started/your_first_component.md), to associate HTML / JS / CSS +files with a component, you can set them as +[`Component.template_name`](../../reference/api.md#django_components.Component.template_name), +[`Component.js_file`](../../reference/api.md#django_components.Component.js_file) +and +[`Component.css_file`](../../reference/api.md#django_components.Component.css_file) respectively: + +```py title="[project root]/components/calendar/calendar.py" from django_components import Component, register @register("calendar") @@ -25,14 +66,21 @@ class Calendar(Component): js_file = "script.js" ``` -In the example above, the files are defined relative to the directory where `component.py` is. +In the example above, we defined the files relative to the directory where the component file is defined. -Alternatively, you can specify the file paths relative to the directories set in `COMPONENTS.dirs` or `COMPONENTS.app_dirs`. +Alternatively, you can specify the file paths relative to the directories set in +[`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs) +or +[`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs). -Assuming that `COMPONENTS.dirs` contains path `[project root]/components`, we can rewrite the example as: +If you specify the paths relative to component's directory, django-componenents does the conversion automatically +for you. -```py -# In a file [project root]/components/calendar/calendar.py +Thus, assuming that +[`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs) +contains path `[project root]/components`, the example above is the same as writing: + +```py title="[project root]/components/calendar/calendar.py" from django_components import Component, register @register("calendar") @@ -42,26 +90,78 @@ class Calendar(Component): js_file = "calendar/script.js" ``` -NOTE: In case of conflict, the preference goes to resolving the files relative to the component's directory. +!!! important -## Defining multiple paths + **File path resolution in-depth** + + At component class creation, django-components checks all file paths defined on the component (e.g. `Component.template_name`). + + For each file path, it checks if the file path is relative to the component's directory. + And such file exists, the component's file path is re-written to be defined relative to a first matching directory + in [`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs) + or + [`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs). + + **Example:** + + ```py title="[root]/components/mytable/mytable.py" + class MyTable(Component): + template_name = "mytable.html" + ``` + + 1. Component `MyTable` is defined in file `[root]/components/mytable/mytable.py`. + 2. The component's directory is thus `[root]/components/mytable/`. + 3. Because `MyTable.template_name` is `mytable.html`, django-components tries to + resolve it as `[root]/components/mytable/mytable.html`. + 4. django-components checks the filesystem. If there's no such file, nothing happens. + 5. If there IS such file, django-components tries to rewrite the path. + 6. django-components searches `COMPONENTS.dirs` and `COMPONENTS.app_dirs` for a first + directory that contains `[root]/components/mytable/mytable.html`. + 7. It comes across `[root]/components/`, which DOES contain the path to `mytable.html`. + 8. Thus, it rewrites `template_name` from `mytable.html` to `mytable/mytable.html`. + + NOTE: In case of ambiguity, the preference goes to resolving the files relative to the component's directory. + +## Defining additional JS and CSS files Each component can have only a single template, and single main JS and CSS. However, you can define additional JS or CSS -using the nested [`Media` class](../../../reference/api#django_components.Component.Media). +using the nested [`Component.Media` class](../../../reference/api#django_components.Component.Media). -This `Media` class behaves similarly to [Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/#assets-as-a-static-definition), -with a few differences: +This `Media` class behaves similarly to +[Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/#assets-as-a-static-definition): -1. Our Media class accepts various formats for the JS and CSS files: either a single file, a list, or (CSS-only) a dictonary (See below) -2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`, [`SafeString`](https://dev.to/doridoro/django-safestring-afj), or a function. -3. Our Media class does NOT support [Django's `extend` keyword](https://docs.djangoproject.com/en/5.1/topics/forms/media/#extend) +- Paths are generally handled as static file paths, and resolved URLs are rendered to HTML with + `media_class.render_js()` or `media_class.render_css()`. +- A path that starts with `http`, `https`, or `/` is considered a URL, skipping the static file resolution. + This path is still rendered to HTML with `media_class.render_js()` or `media_class.render_css()`. +- A [`SafeString`](https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.SafeString), + or a function (with `__html__` method) is considered an already-formatted HTML tag, skipping both static file + resolution and rendering with `media_class.render_js()` or `media_class.render_css()`. +However, there's a few differences from Django's Media class: + +1. Our Media class accepts various formats for the JS and CSS files: either a single file, a list, + or (CSS-only) a dictonary (See [`ComponentMediaInput`](../../../reference/api#django_components.ComponentMediaInput)). +2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`, + [`SafeString`](https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.SafeString), or a function + (See [`ComponentMediaInputPath`](../../../reference/api#django_components.ComponentMediaInputPath)). +3. Our Media class does NOT support + [Django's `extend` keyword](https://docs.djangoproject.com/en/5.1/topics/forms/media/#extend) ```py -class MyComponent(Component): +class MyTable(Component): class Media: - js = ["path/to/script1.js", "path/to/script2.js"] - css = ["path/to/style1.css", "path/to/style2.css"] + js = [ + "path/to/script.js", + "https://unpkg.com/alpinejs@3.14.7/dist/cdn.min.js", # AlpineJS + ] + css = { + "all": [ + "path/to/style.css", + "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", # TailwindCSS + ], + "print": ["path/to/style2.css"], + } ``` ## Configuring CSS Media Types @@ -78,20 +178,29 @@ class MyComponent(Component): class Media: css = { "all": "path/to/style1.css", - "print": "path/to/style2.css", + "print": ["path/to/style2.css", "path/to/style3.css"], } ``` -```py -class MyComponent(Component): - class Media: - css = { - "all": ["path/to/style1.css", "path/to/style2.css"], - "print": ["path/to/style3.css", "path/to/style4.css"], - } -``` +!!! note -NOTE: When you define CSS as a string or a list, the `all` media type is implied. + When you define CSS as a string or a list, the `all` media type is implied. + + So these two examples are the same: + + ```py + class MyComponent(Component): + class Media: + css = "path/to/style1.css" + ``` + + ```py + class MyComponent(Component): + class Media: + css = { + "all": ["path/to/style1.css"], + } + ``` ## Supported types for file paths @@ -103,6 +212,8 @@ File paths can be any of: - `SafeData` (`__html__` method) - `Callable` that returns any of the above, evaluated at class creation (`__new__`) +See [`ComponentMediaInputPath`](../../../reference/api#django_components.ComponentMediaInputPath). + ```py from pathlib import Path @@ -126,18 +237,26 @@ class SimpleComponent(Component): ] ``` -## Path as objects +## Paths as objects -In the example [above](#supported-types-for-file-paths), you could see that when we used `mark_safe` to mark a string as a `SafeString`, we had to define the full ` +# +``` + +If you want to modify the class that is instantiated for [`Component.media`](../../reference/api.md#django_components.Component.media), +you can configure [`Component.media_class`](../../reference/api.md#django_components.Component.media_class) +([See example](#customize-how-paths-are-rendered-into-html-tags)). diff --git a/docs/css/style.css b/docs/css/style.css index d776a117..4fa5b04b 100644 --- a/docs/css/style.css +++ b/docs/css/style.css @@ -11,6 +11,19 @@ h6 { padding-top: 40px; } +.md-typeset h3 { + /* Original styling */ + font-size: 1.25em; + font-weight: 400; + letter-spacing: -.01em; + line-height: 1.5; + margin: 1.6em 0 0.8em; + + /* Custom */ + border-top: 0.5px solid var(--md-typeset-color); + padding-top: 40px; +} + .md-nav__item--section { margin-top: 32px; } diff --git a/src/django_components/__init__.py b/src/django_components/__init__.py index b076b6cf..20087a1c 100644 --- a/src/django_components/__init__.py +++ b/src/django_components/__init__.py @@ -7,6 +7,7 @@ from django_components.app_settings import ContextBehavior, ComponentsSettings from django_components.autodiscovery import autodiscover, import_libraries from django_components.component import Component, ComponentVars, ComponentView +from django_components.component_media import ComponentMediaInput, ComponentMediaInputPath from django_components.component_registry import ( AlreadyRegistered, ComponentRegistry, @@ -44,6 +45,8 @@ __all__ = [ "Component", "ComponentFileEntry", "ComponentFormatter", + "ComponentMediaInput", + "ComponentMediaInputPath", "ComponentRegistry", "ComponentVars", "ComponentView", diff --git a/src/django_components/component.py b/src/django_components/component.py index 2907a3a3..aaa929d1 100644 --- a/src/django_components/component.py +++ b/src/django_components/component.py @@ -212,7 +212,20 @@ class Component( The filepath must be relative to either the file where the component class was defined, or one of the roots of `STATIFILES_DIRS`. - Only one of `template_name`, `get_template_name`, `template` or `get_template` must be defined. + Only one of [`template_name`](../api#django_components.Component.template_name), + [`get_template_name`](../api#django_components.Component.get_template_name), + [`template`](../api#django_components.Component.template) + or [`get_template`](../api#django_components.Component.get_template) must be defined. + + **Example:** + + ```py + class MyComponent(Component): + template_name = "path/to/template.html" + + def get_context_data(self): + return {"name": "World"} + ``` """ def get_template_name(self, context: Context) -> Optional[str]: @@ -222,7 +235,10 @@ class Component( The filepath must be relative to either the file where the component class was defined, or one of the roots of `STATIFILES_DIRS`. - Only one of `template_name`, `get_template_name`, `template` or `get_template` must be defined. + Only one of [`template_name`](../api#django_components.Component.template_name), + [`get_template_name`](../api#django_components.Component.get_template_name), + [`template`](../api#django_components.Component.template) + or [`get_template`](../api#django_components.Component.get_template) must be defined. """ return None @@ -230,14 +246,30 @@ class Component( """ Inlined Django template associated with this component. Can be a plain string or a Template instance. - Only one of `template_name`, `get_template_name`, `template` or `get_template` must be defined. + Only one of [`template_name`](../api#django_components.Component.template_name), + [`get_template_name`](../api#django_components.Component.get_template_name), + [`template`](../api#django_components.Component.template) + or [`get_template`](../api#django_components.Component.get_template) must be defined. + + **Example:** + + ```py + class MyComponent(Component): + template = "Hello, {{ name }}!" + + def get_context_data(self): + return {"name": "World"} + ``` """ def get_template(self, context: Context) -> Optional[Union[str, Template]]: """ Inlined Django template associated with this component. Can be a plain string or a Template instance. - Only one of `template_name`, `get_template_name`, `template` or `get_template` must be defined. + Only one of [`template_name`](../api#django_components.Component.template_name), + [`get_template_name`](../api#django_components.Component.get_template_name), + [`template`](../api#django_components.Component.template) + or [`get_template`](../api#django_components.Component.get_template) must be defined. """ return None @@ -245,50 +277,167 @@ class Component( return cast(DataType, {}) js: Optional[str] = None - """Main JS associated with this component inlined as string.""" + """ + Main JS associated with this component inlined as string. + + Only one of [`js`](../api#django_components.Component.js) or + [`js_file`](../api#django_components.Component.js_file) must be defined. + + **Example:** + + ```py + class MyComponent(Component): + js = "console.log('Hello, World!');" + ``` + """ js_file: Optional[str] = None """ Main JS associated with this component as file path. - When you create a Component subclass, these will happen: + When you create a Component class with `js_file`, these will happen: - 1. The filepath is resolved, in case it is relative to the component Python file. - 2. The file is read and its contents will be available under `MyComponent.js`. + 1. If the file path is relative to the directory where the component's Python file is, + the path is resolved. + 2. The file is read and its contents is set to [`Component.js`](../api#django_components.Component.js). + + Only one of [`js`](../api#django_components.Component.js) or + [`js_file`](../api#django_components.Component.js_file) must be defined. + + **Example:** + + ```js title="path/to/script.js" + console.log('Hello, World!'); + ``` + + ```py title="path/to/component.py" + class MyComponent(Component): + js_file = "path/to/script.js" + + print(MyComponent.js) + # Output: console.log('Hello, World!'); + ``` """ css: Optional[str] = None - """Main CSS associated with this component inlined as string.""" + """ + Main CSS associated with this component inlined as string. + + Only one of [`css`](../api#django_components.Component.css) or + [`css_file`](../api#django_components.Component.css_file) must be defined. + + **Example:** + + ```py + class MyComponent(Component): + css = \"\"\" + .my-class { + color: red; + } + \"\"\" + ``` + """ + css_file: Optional[str] = None """ Main CSS associated with this component as file path. - When you create a Component subclass, these will happen: + When you create a Component class with `css_file`, these will happen: - 1. The filepath is resolved, in case it is relative to the component Python file. - 2. The file is read and its contents will be available under `MyComponent.css`. + 1. If the file path is relative to the directory where the component's Python file is, + the path is resolved. + 2. The file is read and its contents is set to [`Component.css`](../api#django_components.Component.css). + + Only one of [`css`](../api#django_components.Component.css) or + [`css_file`](../api#django_components.Component.css_file) must be defined. + + **Example:** + + ```css title="path/to/style.css" + .my-class { + color: red; + } + ``` + + ```py title="path/to/component.py" + class MyComponent(Component): + css_file = "path/to/style.css" + + print(MyComponent.css) + # Output: + # .my-class { + # color: red; + # }; + ``` """ media: Optional[MediaCls] = None """ Normalized definition of JS and CSS media files associated with this component. - `None` if `Media` is not defined. + `None` if [`Component.Media`](../api#django_components.Component.Media) is not defined. + + This field is generated from [`Component.media_class`](../api#django_components.Component.media_class). + + Read more on [Accessing component's HTML / JS / CSS](../../concepts/fundamentals/defining_js_css_html_files/#accessing-components-media-files). + + **Example:** + + ```py + class MyComponent(Component): + class Media: + js = "path/to/script.js" + css = "path/to/style.css" + + print(MyComponent.media) + # Output: + # + # + ``` + """ # noqa: E501 - NOTE: This field is generated from `Component.media_class`. - """ media_class: Type[MediaCls] = MediaCls + """ + Set the [Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/#assets-as-a-static-definition) + that will be instantiated with the JS and CSS media files from + [`Component.Media`](../api#django_components.Component.Media). + + This is useful when you want to customize the behavior of the media files, like + customizing how the JS or CSS files are rendered into `"), + ] + css = [ + Path("path/to/style.css"), + lambda: "path/to/style.css", + lambda: Path("path/to/style.css"), + ] +``` +""" + + # This is the interface of the class that user is expected to define on the component class, e.g.: # ```py # class MyComponent(Component): @@ -30,7 +64,7 @@ COMP_MEDIA_LAZY_ATTRS = ("media", "template", "template_name", "js", "js_file", # ``` class ComponentMediaInput(Protocol): """ - Defines JS and CSS media files associated with this component. + Defines JS and CSS media files associated with a [`Component`](../api#django_components.Component). ```py class MyTable(Component): @@ -49,8 +83,90 @@ class ComponentMediaInput(Protocol): ``` """ - css: Optional[Union[str, List[str], Dict[str, str], Dict[str, List[str]]]] = None - js: Optional[Union[str, List[str]]] = None + css: Optional[ + Union[ + ComponentMediaInputPath, + List[ComponentMediaInputPath], + Dict[str, ComponentMediaInputPath], + Dict[str, List[ComponentMediaInputPath]], + ] + ] = None + """ + CSS files associated with a [`Component`](../api#django_components.Component). + + - If a string, it's assumed to be a path to a CSS file. + + - If a list, each entry is assumed to be a path to a CSS file. + + - If a dict, the keys are media types (e.g. "all", "print", "screen", etc.), and the values are either: + - A string, assumed to be a path to a CSS file. + - A list, each entry is assumed to be a path to a CSS file. + + Each entry can be a string, bytes, SafeString, PathLike, or a callable that returns one of the former + (see [`ComponentMediaInputPath`](../api#django_components.ComponentMediaInputPath)). + + Examples: + ```py + class MyComponent(Component): + class Media: + css = "path/to/style.css" + ``` + + ```py + class MyComponent(Component): + class Media: + css = ["path/to/style1.css", "path/to/style2.css"] + ``` + + ```py + class MyComponent(Component): + class Media: + css = { + "all": "path/to/style.css", + "print": "path/to/print.css", + } + ``` + + ```py + class MyComponent(Component): + class Media: + css = { + "all": ["path/to/style1.css", "path/to/style2.css"], + "print": "path/to/print.css", + } + ``` + """ + + js: Optional[Union[ComponentMediaInputPath, List[ComponentMediaInputPath]]] = None + """ + JS files associated with a [`Component`](../api#django_components.Component). + + - If a string, it's assumed to be a path to a JS file. + + - If a list, each entry is assumed to be a path to a JS file. + + Each entry can be a string, bytes, SafeString, PathLike, or a callable that returns one of the former + (see [`ComponentMediaInputPath`](../api#django_components.ComponentMediaInputPath)). + + Examples: + ```py + class MyComponent(Component): + class Media: + js = "path/to/script.js" + ``` + + ```py + class MyComponent(Component): + class Media: + js = ["path/to/script1.js", "path/to/script2.js"] + ``` + + ```py + class MyComponent(Component): + class Media: + js = lambda: ["path/to/script1.js", "path/to/script2.js"] + ``` + """ @dataclass @@ -96,7 +212,7 @@ class ComponentMediaMeta(type): def __new__(mcs, name: str, bases: Tuple[Type, ...], attrs: Dict[str, Any]) -> Type: # Normalize the various forms of Media inputs we allow if "Media" in attrs: - normalize_media(attrs["Media"]) + _normalize_media(attrs["Media"]) cls = super().__new__(mcs, name, bases, attrs) comp_cls = cast(Type["Component"], cls) @@ -155,7 +271,7 @@ def _setup_lazy_media_resolve(comp_cls: Type["Component"], attrs: Dict[str, Any] if comp_media is None: continue if not comp_media.resolved: - resolve_media(base, comp_media) + _resolve_media(base, comp_media) value = getattr(comp_media, attr, None) # For each of the pairs of inlined_content + file (e.g. `js` + `js_file`), if at least one of the two @@ -204,7 +320,7 @@ def _setup_lazy_media_resolve(comp_cls: Type["Component"], attrs: Dict[str, Any] setattr(comp_cls, attr, InterceptDescriptor(attr)) -def resolve_media(comp_cls: Type["Component"], comp_media: ComponentMedia) -> None: +def _resolve_media(comp_cls: Type["Component"], comp_media: ComponentMedia) -> None: """ Resolve the media files associated with the component. @@ -272,7 +388,7 @@ def resolve_media(comp_cls: Type["Component"], comp_media: ComponentMedia) -> No comp_media.resolved = True -def normalize_media(media: Type[ComponentMediaInput]) -> None: +def _normalize_media(media: Type[ComponentMediaInput]) -> None: """ Resolve the `Media` class associated with the component. @@ -394,7 +510,7 @@ def _is_media_filepath(filepath: Any) -> bool: return False -def _normalize_media_filepath(filepath: Any) -> Union[str, SafeData]: +def _normalize_media_filepath(filepath: ComponentMediaInputPath) -> Union[str, SafeData]: if callable(filepath): filepath = filepath()