html_compose.resource

Resource import helper classes

For javascript:

  • Manage preload
  • Manage import maps
  • Manage cache busting for local resources
  • Manage SRI hashes

For css:

  • Manage preload
  • Manage cache busting for local resources
  • Manage SRI hashes

For fonts:

  • Manage preconnect
  • Manage preload
  • Manage css @font-face generation or importing from providers i.e. Google Fonts
  1"""
  2Resource import helper classes
  3
  4For javascript:
  5* Manage preload
  6* Manage import maps
  7* Manage cache busting for local resources
  8* Manage SRI hashes
  9
 10For css:
 11* Manage preload
 12* Manage cache busting for local resources
 13* Manage SRI hashes
 14
 15For fonts:
 16* Manage preconnect
 17* Manage preload
 18* Manage css @font-face generation or importing from providers i.e. Google Fonts
 19
 20"""
 21
 22import json
 23from typing import Any, Iterable
 24
 25from .. import base_types, unsafe_text
 26from .. import elements as el
 27
 28
 29class settings:
 30    """
 31    Global settings for js_import/css_import behavior.
 32
 33    `base_dir` is base directory from which local static files are served
 34    and is used to construct cache busting URLs.
 35    """
 36
 37    # Base directory when resolving relative paths for local resources
 38    base_dir = "."
 39    # html-compose cache-buster timestamp
 40    query_string = "hccbts"
 41    # Maximum number of cached URIs for cache busting
 42    cache_cap = 1000
 43    stat_poll_interval: int | float = 1  # seconds
 44
 45
 46class _State:
 47    """
 48    Internal state for local static resource imports
 49    """
 50
 51    stat_cache: dict[str, int | float] = {}
 52
 53    misc_stat_cache: dict[str, int | float] = {}
 54
 55
 56from .css_import import css_import  # noqa: E402
 57from .font_import import font_import_manual, font_import_provider  # noqa: E402
 58from .js_import import js_import  # noqa: E402
 59
 60
 61def to_elements(
 62    js: Iterable[str | js_import] | None = None,
 63    css: Iterable[str | css_import] | None = None,
 64    fonts: Iterable[font_import_manual | font_import_provider] | None = None,
 65):
 66    """
 67    Generate elements for `head` element from resource imports
 68
 69    Depending on your use case consider caching the resolution of this function
 70
 71
 72    :param js: Javascript imports. A string is treated as a simple script src
 73    :param css: CSS imports. A string is treated as a simple link rel=stylesheet
 74    :param fonts: Font imports
 75    """
 76    head_elements: list[base_types.Node] = []
 77
 78    preconnect_links = []
 79    preload_links = []
 80    links = []
 81    if css:
 82        for css_resource in css:
 83            if isinstance(css_resource, css_import):
 84                for link in css_resource.links():
 85                    links.append(link)
 86                for preload in css_resource.preloads():
 87                    preload_links.append(preload)
 88            else:
 89                if not isinstance(css_resource, str):
 90                    raise TypeError("css must be str or css_import")
 91
 92                links.append(el.link(rel="stylesheet", href=css_resource))
 93
 94    script_tags = []
 95    if js:
 96        #  <link rel="modulepreload" href="main.js" />
 97        js_imports: dict[str, str] = {}
 98        scopes: dict[str, dict[str, str]] = {}
 99        for js_src in js:
100            if isinstance(js_src, js_import):
101                jsi: js_import = js_src
102                # Generate script tag
103                script_tags.append(jsi.script())
104                link = jsi.preload_link()
105                if link:
106                    preload_links.append(link)
107
108                entry = jsi.import_map_entry()
109                if entry:
110                    # Add to import map
111                    name, src = entry.name, entry.src
112                    js_imports[name] = src
113                    if entry.scope_urls:
114                        for scope_key in entry.scope_urls:
115                            sdict = scopes.setdefault(scope_key, {})
116                            sdict[name] = src
117
118            else:
119                if not isinstance(js_src, str):
120                    raise TypeError("js must be str or js_import")
121                script_tags.append(el.script(src=js_src))
122
123        if js_imports:
124            import_map: dict[str, Any] = {"imports": js_imports}
125            if scopes:
126                import_map["scopes"] = scopes
127            # dump to json; prevent escapes
128            js_dump = json.dumps(import_map).replace("<", "\\u003c")
129            preload_links.insert(
130                0, el.script(type="importmap")[unsafe_text(js_dump)]
131            )
132    style_text: list[str] = []
133    if fonts:
134        for font in fonts:
135            # Each font may be a different kind but the interface means
136            # we capture anything they generate
137            links.extend(font.links())
138            preconnect_links.extend(font.preconnect_links())
139            preload_links.extend(font.preload_links())
140            style_data = font.style_data()
141            if style_data:
142                style_text.append(style_data)
143
144    if preconnect_links:
145        head_elements.extend(preconnect_links)
146
147    if preload_links:
148        head_elements.extend(preload_links)
149
150    if links:
151        head_elements.extend(links)
152
153    if style_text:
154        head_elements.append(el.style()[unsafe_text("\n".join(style_text))])
155
156    if script_tags:
157        head_elements.extend(script_tags)
158
159    return head_elements
160
161
162__all__ = [
163    "js_import",
164    "css_import",
165    "font_import_manual",
166    "font_import_provider",
167    "to_elements",
168    "settings",
169]
class js_import:
 19class js_import:
 20    """
 21    A javascript import helper class which employs one or more of:
 22
 23    - `script(` to define the import
 24    - `importmap` which maps javascript module names to URLs
 25    Setting the name parameter like so
 26    ```python
 27    js_import(
 28        "https://cdn.jsdelivr.net/npm/alpinejs@3.15.0/+esm",
 29        name="alpinejs")
 30    ```
 31    creates an import map so javascript modules can be imported by name
 32    - `link(rel="modulepreload")` to preload the import
 33    - if cache_bust is true, appends a timestamp to the URL to
 34
 35    An production usage might look like:
 36
 37    ```python
 38    [
 39    js_import('./static/admin.js',
 40                name='admin',
 41                cache_bust=True),
 42
 43    js_import(
 44        "https://cdn.jsdelivr.net/npm/alpinejs@3.15.0/+esm",
 45        name="alpinejs",
 46        preload=True,
 47        hash=(
 48            "sha384-Yf57wlxlrA1+0X6Ye9NOBxQ1tpmiwI/"
 49            "9mFpv9tT/Rh2UAajwwAlTWHnvTGYhgv7p"
 50            ),
 51        crossorigin="anonymous"
 52        ),
 53
 54    ]
 55    ```
 56
 57    ...
 58
 59    ## Usage in window:
 60
 61    ```python
 62    script(type="module")[
 63        '''
 64        import Alpine from 'alpinejs'
 65        window.Alpine = Alpine
 66        Alpine.start()
 67        '''
 68    ]
 69    ```
 70
 71    ## Generated "HTML":
 72
 73    ```python
 74    import json
 75    from html_compose import el, unsafe_text
 76
 77    [
 78        el.script(type="importmap")[
 79            unsafe_text('{"imports": {"admin": "./static/admin.js?ts=1760157623", "alpinejs": "https://cdn.jsdelivr.net/npm/alpinejs@3.15.0/+esm"}}')
 80        ],
 81
 82        el.link(href="./static/admin.js?ts=1760157623", rel=["modulepreload"]),
 83
 84
 85        el.link(
 86            href="https://cdn.jsdelivr.net/npm/alpinejs@3.15.0/+esm",
 87            integrity="sha384-Yf57wlxlrA1+0X6Ye9NOBxQ1tpmiwI/9mFpv9tT/Rh2UAajwwAlTWHnvTGYhgv7p",
 88            crossorigin="anonymous",
 89            rel=["modulepreload"],
 90        ),
 91
 92        el.script(src="./static/admin.js?ts=1760157623", type="module"),
 93
 94        el.script(
 95            src="https://cdn.jsdelivr.net/npm/alpinejs@3.15.0/+esm",
 96            type="module",
 97            integrity="sha384-Yf57wlxlrA1+0X6Ye9NOBxQ1tpmiwI/9mFpv9tT/Rh2UAajwwAlTWHnvTGYhgv7p",
 98            crossorigin="anonymous",
 99        ),
100    ]
101    ```
102
103    See:
104    - https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap
105    - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
106    - https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/modulepreload
107    - https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/preload
108    - https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
109    - https://www.srihash.org/
110
111    """
112
113    def __init__(
114        self,
115        source: str,
116        name: str | None = None,
117        preload: bool = False,
118        hash: str | None = None,
119        async_: bool = False,
120        defer: bool = False,
121        nomodule: bool = False,
122        scope_url: str | Iterable[str] | None = None,
123        crossorigin: Literal["", "anonymous", "user-credentials"]
124        | str
125        | None = None,
126        cache_bust: bool = False,
127    ):
128        """
129        A javascript import wrapper to manage optimal window import strategies.
130
131        If name is set, the import is added to an import map and imported
132        as a module.
133
134        Parameters
135        ----------
136        `source`:
137            The literal src passed to tags i.e. script and link preload.
138
139            If cache_bust is set, a timestamp is appended to the URL.
140        `name`:
141            The name of the import, e.g. "lodash" which can be used in
142            javascript type="module" imports.
143
144            Doing this automatically changes the script type to module
145
146        `preload`:
147            The modulepreload keyword, for the rel attribute of the `<link/>`
148            element, provides a declarative way to preemptively fetch a module
149            script, parse and compile it, and store it in the document's module
150            map for later execution.
151
152            If this is not a javascript module (i.e. name is not set),
153            the rel attribute is set to "preload" and the as_ attribute
154            is set to "script" instead.
155
156        `async_`:
157            The async attribute causes the script to be executed asynchronously as soon as it is available,
158            without blocking HTML parsing.
159
160        `defer`:
161            Defers script execution until document parsing is complete.
162            Has no effect on module scripts (they are deferred by default).
163
164        `nomodule`:
165            Added to script tag.
166
167            Indicates that the script should not be executed in browsers that
168            support ES modules — in effect, this can be used to serve fallback
169            scripts to older browsers that do not support javascript modules.
170
171        `hash`:
172            An optional SRI integrity hash for the import
173
174        `crossorigin`:
175            Optionally sets the crossorigin attribute on the script tag.
176            Valid values are "", "anonymous", "use-credentials"
177
178        `cache_bust`:
179            If true, appends a timestamp to the URL to work with browser
180            caching. Webservers configured for static resources manage this
181            feature automatically, but for development this can be useful.
182
183            This feature only works for local resources, i.e. those that
184            exist relative to `resource.settings.base_dir`
185
186        `scope_url`:
187            Optional route or list of paths where this import should be
188            included.
189
190            Scopes let you have different mappings for different parts of your
191            app.
192
193            Because they affect the import map but not what is loaded -
194            the script tag controls what loads - they are rarely used.
195
196        """
197
198        self.name = name
199        self.source = source
200        self.preload = preload
201        self.hash = hash
202        self.scope_urls = scope_url
203        if scope_url:
204            if isinstance(scope_url, str):
205                self.scope_urls = [scope_url]
206            elif is_iterable_but_not_str(scope_url):
207                # Materialize scope URLs so they can be iterated multiple times
208                # (flatten_iterable returns a generator by default).
209                self.scope_urls = list(flatten_iterable(scope_url))
210            else:
211                self.scope_urls = [str(scope_url)]
212        self.crossorigin = crossorigin
213        self.cache_bust = cache_bust
214        self.has_link = preload
215        self.async_ = async_
216        self.defer = defer
217        self.nomodule = nomodule
218        self._src = self.uri()
219        if hash and self.crossorigin is None:
220            raise ValueError(
221                "If hash is set, crossorigin must be set to ''/'anonymous'"
222            )
223
224    def uri(self) -> str:
225        """
226        Returns the source URI - with cache busting if enabled
227        which is implemented by getting the mtime of the local file
228
229        This operation performs up to two fs stats per call
230
231        1. The base static directory is checked once and cached per interpreter
232        2. The specific resource is checked if it is time to poll again
233           based on settings.stat_poll_interval
234        """
235        if not self.cache_bust:
236            return self.source
237
238        return _cachebust_resource_uri(self.source)
239
240    def import_map_entry(self) -> _ImportMapEntry | None:
241        """
242        Returns a tuple of (name, source, scope_url) for use in an import map
243        """
244
245        if self.name:
246            if self.scope_urls is None:
247                return _ImportMapEntry(name=self.name, src=self._src)
248            else:
249                return _ImportMapEntry(
250                    name=self.name, src=self._src, scope_urls=self.scope_urls
251                )
252
253        return None
254
255    def preload_link(self) -> el.link | None:
256        """
257        Returns one or more link element for this import
258        """
259        if self.preload:
260            attrs = {"href": self._src}
261            if self.hash:
262                attrs["integrity"] = self.hash
263            if self.crossorigin:
264                attrs["crossorigin"] = self.crossorigin
265            if self.name:
266                return el.link(attrs=attrs, rel="modulepreload")
267            else:
268                return el.link(attrs=attrs, rel="preload", as_="script")
269
270        return None
271
272    def script(self) -> el.script:
273        """
274        Returns a script tag for this import
275        """
276        attrs: dict[str, str | bool] = {"src": self._src}
277
278        if self.name:
279            attrs["type"] = "module"
280        if self.hash:
281            attrs["integrity"] = self.hash
282        if self.crossorigin:
283            attrs["crossorigin"] = self.crossorigin
284        if self.async_:
285            attrs["async"] = True
286        if self.defer and not self.name:
287            # Only set defer for non-modules
288            attrs["defer"] = True
289        if self.nomodule:
290            attrs["nomodule"] = True
291
292        return el.script(attrs=attrs)

A javascript import helper class which employs one or more of:

  • script( to define the import
  • importmap which maps javascript module names to URLs Setting the name parameter like so
js_import(
    "https://cdn.jsdelivr.net/npm/alpinejs@3.15.0/+esm",
    name="alpinejs")

creates an import map so javascript modules can be imported by name

  • link(rel="modulepreload") to preload the import
  • if cache_bust is true, appends a timestamp to the URL to

An production usage might look like:

[
js_import('./static/admin.js',
            name='admin',
            cache_bust=True),

js_import(
    "https://cdn.jsdelivr.net/npm/alpinejs@3.15.0/+esm",
    name="alpinejs",
    preload=True,
    hash=(
        "sha384-Yf57wlxlrA1+0X6Ye9NOBxQ1tpmiwI/"
        "9mFpv9tT/Rh2UAajwwAlTWHnvTGYhgv7p"
        ),
    crossorigin="anonymous"
    ),

]

...

Usage in window:

script(type="module")[
    '''
    import Alpine from 'alpinejs'
    window.Alpine = Alpine
    Alpine.start()
    '''
]

Generated "HTML":

import json
from html_compose import el, unsafe_text

[
    el.script(type="importmap")[
        unsafe_text('{"imports": {"admin": "./static/admin.js?ts=1760157623", "alpinejs": "https://cdn.jsdelivr.net/npm/alpinejs@3.15.0/+esm"}}')
    ],

    el.link(href="./static/admin.js?ts=1760157623", rel=["modulepreload"]),


    el.link(
        href="https://cdn.jsdelivr.net/npm/alpinejs@3.15.0/+esm",
        integrity="sha384-Yf57wlxlrA1+0X6Ye9NOBxQ1tpmiwI/9mFpv9tT/Rh2UAajwwAlTWHnvTGYhgv7p",
        crossorigin="anonymous",
        rel=["modulepreload"],
    ),

    el.script(src="./static/admin.js?ts=1760157623", type="module"),

    el.script(
        src="https://cdn.jsdelivr.net/npm/alpinejs@3.15.0/+esm",
        type="module",
        integrity="sha384-Yf57wlxlrA1+0X6Ye9NOBxQ1tpmiwI/9mFpv9tT/Rh2UAajwwAlTWHnvTGYhgv7p",
        crossorigin="anonymous",
    ),
]

See:

js_import( source: str, name: str | None = None, preload: bool = False, hash: str | None = None, async_: bool = False, defer: bool = False, nomodule: bool = False, scope_url: Union[str, Iterable[str], NoneType] = None, crossorigin: Union[Literal['', 'anonymous', 'user-credentials'], str, NoneType] = None, cache_bust: bool = False)
113    def __init__(
114        self,
115        source: str,
116        name: str | None = None,
117        preload: bool = False,
118        hash: str | None = None,
119        async_: bool = False,
120        defer: bool = False,
121        nomodule: bool = False,
122        scope_url: str | Iterable[str] | None = None,
123        crossorigin: Literal["", "anonymous", "user-credentials"]
124        | str
125        | None = None,
126        cache_bust: bool = False,
127    ):
128        """
129        A javascript import wrapper to manage optimal window import strategies.
130
131        If name is set, the import is added to an import map and imported
132        as a module.
133
134        Parameters
135        ----------
136        `source`:
137            The literal src passed to tags i.e. script and link preload.
138
139            If cache_bust is set, a timestamp is appended to the URL.
140        `name`:
141            The name of the import, e.g. "lodash" which can be used in
142            javascript type="module" imports.
143
144            Doing this automatically changes the script type to module
145
146        `preload`:
147            The modulepreload keyword, for the rel attribute of the `<link/>`
148            element, provides a declarative way to preemptively fetch a module
149            script, parse and compile it, and store it in the document's module
150            map for later execution.
151
152            If this is not a javascript module (i.e. name is not set),
153            the rel attribute is set to "preload" and the as_ attribute
154            is set to "script" instead.
155
156        `async_`:
157            The async attribute causes the script to be executed asynchronously as soon as it is available,
158            without blocking HTML parsing.
159
160        `defer`:
161            Defers script execution until document parsing is complete.
162            Has no effect on module scripts (they are deferred by default).
163
164        `nomodule`:
165            Added to script tag.
166
167            Indicates that the script should not be executed in browsers that
168            support ES modules — in effect, this can be used to serve fallback
169            scripts to older browsers that do not support javascript modules.
170
171        `hash`:
172            An optional SRI integrity hash for the import
173
174        `crossorigin`:
175            Optionally sets the crossorigin attribute on the script tag.
176            Valid values are "", "anonymous", "use-credentials"
177
178        `cache_bust`:
179            If true, appends a timestamp to the URL to work with browser
180            caching. Webservers configured for static resources manage this
181            feature automatically, but for development this can be useful.
182
183            This feature only works for local resources, i.e. those that
184            exist relative to `resource.settings.base_dir`
185
186        `scope_url`:
187            Optional route or list of paths where this import should be
188            included.
189
190            Scopes let you have different mappings for different parts of your
191            app.
192
193            Because they affect the import map but not what is loaded -
194            the script tag controls what loads - they are rarely used.
195
196        """
197
198        self.name = name
199        self.source = source
200        self.preload = preload
201        self.hash = hash
202        self.scope_urls = scope_url
203        if scope_url:
204            if isinstance(scope_url, str):
205                self.scope_urls = [scope_url]
206            elif is_iterable_but_not_str(scope_url):
207                # Materialize scope URLs so they can be iterated multiple times
208                # (flatten_iterable returns a generator by default).
209                self.scope_urls = list(flatten_iterable(scope_url))
210            else:
211                self.scope_urls = [str(scope_url)]
212        self.crossorigin = crossorigin
213        self.cache_bust = cache_bust
214        self.has_link = preload
215        self.async_ = async_
216        self.defer = defer
217        self.nomodule = nomodule
218        self._src = self.uri()
219        if hash and self.crossorigin is None:
220            raise ValueError(
221                "If hash is set, crossorigin must be set to ''/'anonymous'"
222            )

A javascript import wrapper to manage optimal window import strategies.

If name is set, the import is added to an import map and imported as a module.

Parameters

source: The literal src passed to tags i.e. script and link preload.

If cache_bust is set, a timestamp is appended to the URL.

name: The name of the import, e.g. "lodash" which can be used in javascript type="module" imports.

Doing this automatically changes the script type to module

preload: The modulepreload keyword, for the rel attribute of the <link/> element, provides a declarative way to preemptively fetch a module script, parse and compile it, and store it in the document's module map for later execution.

If this is not a javascript module (i.e. name is not set),
the rel attribute is set to "preload" and the as_ attribute
is set to "script" instead.

async_: The async attribute causes the script to be executed asynchronously as soon as it is available, without blocking HTML parsing.

defer: Defers script execution until document parsing is complete. Has no effect on module scripts (they are deferred by default).

nomodule: Added to script tag.

Indicates that the script should not be executed in browsers that
support ES modules — in effect, this can be used to serve fallback
scripts to older browsers that do not support javascript modules.

hash: An optional SRI integrity hash for the import

crossorigin: Optionally sets the crossorigin attribute on the script tag. Valid values are "", "anonymous", "use-credentials"

cache_bust: If true, appends a timestamp to the URL to work with browser caching. Webservers configured for static resources manage this feature automatically, but for development this can be useful.

This feature only works for local resources, i.e. those that
exist relative to `resource.settings.base_dir`

scope_url: Optional route or list of paths where this import should be included.

Scopes let you have different mappings for different parts of your
app.

Because they affect the import map but not what is loaded -
the script tag controls what loads - they are rarely used.
name
source
preload
hash
scope_urls
crossorigin
cache_bust
async_
defer
nomodule
def uri(self) -> str:
224    def uri(self) -> str:
225        """
226        Returns the source URI - with cache busting if enabled
227        which is implemented by getting the mtime of the local file
228
229        This operation performs up to two fs stats per call
230
231        1. The base static directory is checked once and cached per interpreter
232        2. The specific resource is checked if it is time to poll again
233           based on settings.stat_poll_interval
234        """
235        if not self.cache_bust:
236            return self.source
237
238        return _cachebust_resource_uri(self.source)

Returns the source URI - with cache busting if enabled which is implemented by getting the mtime of the local file

This operation performs up to two fs stats per call

  1. The base static directory is checked once and cached per interpreter
  2. The specific resource is checked if it is time to poll again based on settings.stat_poll_interval
def import_map_entry(self) -> html_compose.resource.js_import._ImportMapEntry | None:
240    def import_map_entry(self) -> _ImportMapEntry | None:
241        """
242        Returns a tuple of (name, source, scope_url) for use in an import map
243        """
244
245        if self.name:
246            if self.scope_urls is None:
247                return _ImportMapEntry(name=self.name, src=self._src)
248            else:
249                return _ImportMapEntry(
250                    name=self.name, src=self._src, scope_urls=self.scope_urls
251                )
252
253        return None

Returns a tuple of (name, source, scope_url) for use in an import map

def script(self) -> html_compose.elements.script_element.script:
272    def script(self) -> el.script:
273        """
274        Returns a script tag for this import
275        """
276        attrs: dict[str, str | bool] = {"src": self._src}
277
278        if self.name:
279            attrs["type"] = "module"
280        if self.hash:
281            attrs["integrity"] = self.hash
282        if self.crossorigin:
283            attrs["crossorigin"] = self.crossorigin
284        if self.async_:
285            attrs["async"] = True
286        if self.defer and not self.name:
287            # Only set defer for non-modules
288            attrs["defer"] = True
289        if self.nomodule:
290            attrs["nomodule"] = True
291
292        return el.script(attrs=attrs)

Returns a script tag for this import

class css_import:
  8class css_import:
  9    """
 10    A css import helper class which employs:
 11    - `link(rel="stylesheet")` to define the import
 12    - `link(rel="preload")` to preload the import
 13    - `hash` and `crossorigin` for SRI
 14    - Local resource cache busting
 15
 16    An production usage might look like:
 17    ```python
 18    [
 19    css_import('./static/admin.css',
 20                cache_bust=True),
 21
 22    css_import(
 23        "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css",
 24        hash=(
 25            "sha384-9ndCyUaIbzAi2FUVXJi0CjmCapS"
 26            "mO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
 27            ),
 28        crossorigin="anonymous",
 29        preload=True
 30    )
 31
 32    ]
 33    ```
 34
 35    ## Generated "HTML":
 36
 37    ```python
 38    import json
 39    from html_compose import el
 40
 41    [
 42        link(rel='preload',
 43            href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css',
 44            as_='style',
 45            integrity='sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7'
 46            'SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM',
 47            crossorigin='anonymous'),
 48
 49        link(rel='stylesheet', href='./static/admin.css?ts=1760153426'),
 50
 51        link(rel='stylesheet',
 52            href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css',
 53            integrity='sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7'
 54            'SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM',
 55            crossorigin='anonymous')
 56    ]
 57    ```
 58
 59    See:
 60        https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/preload
 61        https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
 62        https://www.srihash.org/
 63
 64    """
 65
 66    def __init__(
 67        self,
 68        href: str,
 69        preload: bool = False,
 70        hash: str | None = None,
 71        crossorigin: Literal["", "anonymous", "user-credentials"]
 72        | str
 73        | None = None,
 74        cache_bust: bool = False,
 75    ):
 76        """
 77        A css import wrapper to wrap some of the complexity of optimal
 78        resource loading.
 79
 80
 81        Parameters
 82        ----------
 83        `href`:
 84            The literal href passed to the link tag
 85
 86        `preload`:
 87            If true, adds a preload link for this resource
 88
 89        `hash`:
 90            An optional SRI integrity hash for the import
 91
 92        `crossorigin`:
 93            Optionally sets the crossorigin attribute on the link tag.
 94            Valid values are "", "anonymous", "use-credentials"
 95
 96        `cache_bust`:
 97            If true, appends a timestamp to the URL to prevent browser
 98            caching. Webservers configured for static resources manage this
 99            feature automatically, but for development this can be useful.
100
101            This feature only works for local resources, i.e. those that
102            exist in `resource.settings.base_dir`
103
104        """
105        self.href = href
106        self.preload = preload
107        self.hash = hash
108        self.crossorigin = crossorigin
109        self.cache_bust = cache_bust
110        self.has_link = preload
111        self._href = self.uri()
112
113        if hash and self.crossorigin is None:
114            raise ValueError(
115                "If hash is set, crossorigin must be set to ''/'anonymous'"
116            )
117
118    def uri(self):
119        """
120        Returns the source URI - with cache busting if enabled
121        which is implemented by getting the mtime of the local file
122
123        This operation performs up to two fs stats per call
124
125        1. The base static directory is checked once and cached per interpreter
126        2. The specific resource is checked if it is time to poll again
127           based on settings.stat_poll_interval
128        """
129        if not self.cache_bust:
130            return self.href
131
132        return _cachebust_resource_uri(self.href)
133
134    def preloads(self) -> list[el.link]:
135        """
136        Returns a link element for preloading this import if preload is set
137        """
138        attrs = {"href": self._href}
139        if self.hash:
140            attrs["integrity"] = self.hash
141        if self.crossorigin:
142            attrs["crossorigin"] = self.crossorigin
143        if self.preload:
144            return [el.link(attrs=attrs, rel="preload", as_="style")]
145
146        return []
147
148    def links(self):
149        """
150        Returns one or more link element for this import
151        """
152        links = []
153        attrs = {"href": self._href}
154        if self.hash:
155            attrs["integrity"] = self.hash
156        if self.crossorigin:
157            attrs["crossorigin"] = self.crossorigin
158
159        links.append(el.link(attrs=attrs, rel="stylesheet"))
160
161        return links

A css import helper class which employs:

  • link(rel="stylesheet") to define the import
  • link(rel="preload") to preload the import
  • hash and crossorigin for SRI
  • Local resource cache busting

An production usage might look like:

[
css_import('./static/admin.css',
            cache_bust=True),

css_import(
    "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css",
    hash=(
        "sha384-9ndCyUaIbzAi2FUVXJi0CjmCapS"
        "mO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
        ),
    crossorigin="anonymous",
    preload=True
)

]

Generated "HTML":

import json
from html_compose import el

[
    link(rel='preload',
        href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css',
        as_='style',
        integrity='sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7'
        'SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM',
        crossorigin='anonymous'),

    link(rel='stylesheet', href='./static/admin.css?ts=1760153426'),

    link(rel='stylesheet',
        href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css',
        integrity='sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7'
        'SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM',
        crossorigin='anonymous')
]

See: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/preload https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity https://www.srihash.org/

css_import( href: str, preload: bool = False, hash: str | None = None, crossorigin: Union[Literal['', 'anonymous', 'user-credentials'], str, NoneType] = None, cache_bust: bool = False)
 66    def __init__(
 67        self,
 68        href: str,
 69        preload: bool = False,
 70        hash: str | None = None,
 71        crossorigin: Literal["", "anonymous", "user-credentials"]
 72        | str
 73        | None = None,
 74        cache_bust: bool = False,
 75    ):
 76        """
 77        A css import wrapper to wrap some of the complexity of optimal
 78        resource loading.
 79
 80
 81        Parameters
 82        ----------
 83        `href`:
 84            The literal href passed to the link tag
 85
 86        `preload`:
 87            If true, adds a preload link for this resource
 88
 89        `hash`:
 90            An optional SRI integrity hash for the import
 91
 92        `crossorigin`:
 93            Optionally sets the crossorigin attribute on the link tag.
 94            Valid values are "", "anonymous", "use-credentials"
 95
 96        `cache_bust`:
 97            If true, appends a timestamp to the URL to prevent browser
 98            caching. Webservers configured for static resources manage this
 99            feature automatically, but for development this can be useful.
100
101            This feature only works for local resources, i.e. those that
102            exist in `resource.settings.base_dir`
103
104        """
105        self.href = href
106        self.preload = preload
107        self.hash = hash
108        self.crossorigin = crossorigin
109        self.cache_bust = cache_bust
110        self.has_link = preload
111        self._href = self.uri()
112
113        if hash and self.crossorigin is None:
114            raise ValueError(
115                "If hash is set, crossorigin must be set to ''/'anonymous'"
116            )

A css import wrapper to wrap some of the complexity of optimal resource loading.

Parameters

href: The literal href passed to the link tag

preload: If true, adds a preload link for this resource

hash: An optional SRI integrity hash for the import

crossorigin: Optionally sets the crossorigin attribute on the link tag. Valid values are "", "anonymous", "use-credentials"

cache_bust: If true, appends a timestamp to the URL to prevent browser caching. Webservers configured for static resources manage this feature automatically, but for development this can be useful.

This feature only works for local resources, i.e. those that
exist in `resource.settings.base_dir`
href
preload
hash
crossorigin
cache_bust
def uri(self):
118    def uri(self):
119        """
120        Returns the source URI - with cache busting if enabled
121        which is implemented by getting the mtime of the local file
122
123        This operation performs up to two fs stats per call
124
125        1. The base static directory is checked once and cached per interpreter
126        2. The specific resource is checked if it is time to poll again
127           based on settings.stat_poll_interval
128        """
129        if not self.cache_bust:
130            return self.href
131
132        return _cachebust_resource_uri(self.href)

Returns the source URI - with cache busting if enabled which is implemented by getting the mtime of the local file

This operation performs up to two fs stats per call

  1. The base static directory is checked once and cached per interpreter
  2. The specific resource is checked if it is time to poll again based on settings.stat_poll_interval
def preloads(self) -> list[html_compose.elements.link_element.link]:
134    def preloads(self) -> list[el.link]:
135        """
136        Returns a link element for preloading this import if preload is set
137        """
138        attrs = {"href": self._href}
139        if self.hash:
140            attrs["integrity"] = self.hash
141        if self.crossorigin:
142            attrs["crossorigin"] = self.crossorigin
143        if self.preload:
144            return [el.link(attrs=attrs, rel="preload", as_="style")]
145
146        return []

Returns a link element for preloading this import if preload is set

class font_import_manual(html_compose.resource.font_import._font_import_base):
 45class font_import_manual(_font_import_base):
 46    def __init__(
 47        self,
 48        hrefs: str | Iterable[str],
 49        family: str,
 50        weight: int
 51        | tuple
 52        | str
 53        | Literal["bold", "light", "lighter", "bolder", "normal"]
 54        | None = "normal",
 55        style: Literal["normal", "italic", "oblique"] | str = "normal",
 56        display: Literal["swap", "optional", "fallback", "auto", "block"]
 57        | str = "swap",
 58        preload: bool = True,
 59        crossorigin: Literal["", "anonymous", "use-credentials"] | None = None,
 60        unicode_range: str | None = None,
 61        cache_bust: bool = False,
 62    ) -> None:
 63        """
 64        Declare a web font via @font-face and (optionally) emit
 65        a preload `<link>`.
 66        This initializer targets the direct-file flow (you supply the exact .woff2 URL).
 67
 68        If multiple hrefs are provided, preload must choose only one and so
 69        the first href is used for preload and the rest are only
 70        in the @font-face.
 71
 72        Values are passed verbatim. NEVER use this with untrusted user input.
 73
 74        Parameters
 75        ----------
 76
 77        `hrefs`:
 78            1 or more URL to the font file (typically .woff2).
 79            Pass a str for a single URL, or an iterable of str for multiple URLs.
 80
 81            The @font-face `src` will reference this URL verbatim.
 82
 83        `family`:
 84            CSS `font-family` name to expose (e.g., "Noto Sans").
 85            Do not include quotes; they are added automatically.
 86
 87        `weight`:
 88            Single numeric weight (e.g., 400) for a static face, or a `(low, high)`
 89            tuple (e.g., `(100, 900)`) for a variable font range.
 90
 91        `style`:
 92            Font style for this face: `"normal"` or `"italic"`. Declare another
 93            instance for the other style if needed.
 94
 95        `display`:
 96            `font-display` strategy. `"swap"` is a safe default to avoid FOIT.
 97
 98        `preload`:
 99            If `True`, also create a `<link rel="preload" as="font" ...>` for `href`.
100            Preload only warms the fetch; the @font-face rule still does the actual use.
101
102        `crossorigin`:
103            CORS mode for cross-origin fonts. When unset, it is
104            fetched as same-origin Use `""` or `anonymous` for most CDN/remote
105            cases, or `"use-credentials"` to pass cookies if strictly necessary.
106
107        `unicode_range`:
108            Optional CSS `unicode-range` (e.g., `"U+0000-00FF"`) to subset coverage.
109        """
110        if isinstance(hrefs, str):
111            hrefs = [hrefs]
112        self.hrefs = hrefs
113        self.weight = weight
114        self.style = style
115        self.display = display
116        self.preload = preload
117        self.crossorigin = crossorigin
118        self.cache_bust = cache_bust
119        self.has_link = preload
120        self.unicode_range = unicode_range
121        self._hrefs = self.uris()
122
123        # Escape most stuff that could break out of quotes
124        safe_family = (
125            family.replace("\\", "\\\\").replace("'", "\\'").replace('"', '\\"')
126        )
127        self.family = safe_family
128
129    def uris(self) -> list[str]:  # -> list[str] | list[Any]:
130        """
131        Returns the source URI - with cache busting if enabled
132        which is implemented by getting the mtime of the local file
133
134        This operation performs up to two fs stats per call
135
136        1. The base static directory is checked once and cached per interpreter
137        2. The specific resource is checked if it is time to poll again
138           based on settings.stat_poll_interval
139        """
140        hrefs = []
141        for h in self.hrefs:
142            if not self.cache_bust:
143                hrefs.append(h)
144                continue
145
146            hrefs.append(_cachebust_resource_uri(h))
147        return hrefs
148
149    @staticmethod
150    def get_font_format(href: str) -> str | None:
151        ext = href.split(".")[-1].lower()
152        return _AUTODETECT_CSS_FONT_TYPES.get(ext, None)
153
154    @staticmethod
155    def get_font_mime(href: str) -> str | None:
156        ext = href.split(".")[-1].lower()
157        return _AUTODETECT_FONT_MIMETYPES.get(ext, None)
158
159    def preload_links(self) -> list[el.link]:
160        """
161        Returns preload links if preload is set
162        """
163        if not self.preload:
164            return []
165
166        first_href = self._hrefs[0]
167        attrs = {"href": first_href}
168
169        if self.crossorigin:
170            attrs["crossorigin"] = self.crossorigin
171
172        mimetype = font_import_manual.get_font_mime(first_href)
173
174        if mimetype:
175            attrs["type"] = mimetype
176
177        return [el.link(attrs=attrs, rel="preload", as_="font")]
178
179    def links(self) -> list[el.link]:
180        return []
181
182    def style_data(self) -> str:
183        """
184        Returns one or more nodes for this import
185        """
186
187        font_refs = []
188
189        if len(self._hrefs) == 1:
190            # we don't have to hint format if there's only one
191            font_refs.append(f"url('{self._hrefs[0]}')")
192        else:
193            for h in self._hrefs:
194                entry = f"url('{h}')"
195                format = font_import_manual.get_font_format(h)
196                if format:
197                    entry += f" format('{format}')"
198
199                font_refs.append(entry)
200
201        if isinstance(self.weight, tuple):
202            font_weight = f"{self.weight[0]} {self.weight[1]}"
203        elif isinstance(self.weight, str):
204            font_weight = self.weight
205        elif isinstance(self.weight, int):
206            font_weight = str(self.weight)
207        else:
208            raise TypeError("font weight must be int, str, or tuple")
209
210        style_attrs = {
211            "font-family": f"'{self.family}'",
212            "src": ",\n\t\t".join(font_refs),
213            "font-style": self.style,
214            "font-display": self.display,
215            "font-weight": font_weight,
216            "unicode-range": self.unicode_range,
217        }
218        for k in list(style_attrs.keys()):
219            v = style_attrs[k]
220            if v is None:
221                del style_attrs[k]
222
223        return "\n".join(
224            [
225                "@font-face {",
226                "".join(
227                    [
228                        "\t",
229                        ";\n\t".join(
230                            [f"{k}: {v}" for k, v in style_attrs.items()]
231                        ),
232                        ";",  # Ensure last property ends with semicolon
233                    ]
234                ),
235                "}",
236            ]
237        )
238
239    def preconnect_links(self) -> list[el.link]:
240        return []
font_import_manual( hrefs: Union[str, Iterable[str]], family: str, weight: Union[int, tuple, str, Literal['bold', 'light', 'lighter', 'bolder', 'normal'], NoneType] = 'normal', style: Union[Literal['normal', 'italic', 'oblique'], str] = 'normal', display: Union[Literal['swap', 'optional', 'fallback', 'auto', 'block'], str] = 'swap', preload: bool = True, crossorigin: Optional[Literal['', 'anonymous', 'use-credentials']] = None, unicode_range: str | None = None, cache_bust: bool = False)
 46    def __init__(
 47        self,
 48        hrefs: str | Iterable[str],
 49        family: str,
 50        weight: int
 51        | tuple
 52        | str
 53        | Literal["bold", "light", "lighter", "bolder", "normal"]
 54        | None = "normal",
 55        style: Literal["normal", "italic", "oblique"] | str = "normal",
 56        display: Literal["swap", "optional", "fallback", "auto", "block"]
 57        | str = "swap",
 58        preload: bool = True,
 59        crossorigin: Literal["", "anonymous", "use-credentials"] | None = None,
 60        unicode_range: str | None = None,
 61        cache_bust: bool = False,
 62    ) -> None:
 63        """
 64        Declare a web font via @font-face and (optionally) emit
 65        a preload `<link>`.
 66        This initializer targets the direct-file flow (you supply the exact .woff2 URL).
 67
 68        If multiple hrefs are provided, preload must choose only one and so
 69        the first href is used for preload and the rest are only
 70        in the @font-face.
 71
 72        Values are passed verbatim. NEVER use this with untrusted user input.
 73
 74        Parameters
 75        ----------
 76
 77        `hrefs`:
 78            1 or more URL to the font file (typically .woff2).
 79            Pass a str for a single URL, or an iterable of str for multiple URLs.
 80
 81            The @font-face `src` will reference this URL verbatim.
 82
 83        `family`:
 84            CSS `font-family` name to expose (e.g., "Noto Sans").
 85            Do not include quotes; they are added automatically.
 86
 87        `weight`:
 88            Single numeric weight (e.g., 400) for a static face, or a `(low, high)`
 89            tuple (e.g., `(100, 900)`) for a variable font range.
 90
 91        `style`:
 92            Font style for this face: `"normal"` or `"italic"`. Declare another
 93            instance for the other style if needed.
 94
 95        `display`:
 96            `font-display` strategy. `"swap"` is a safe default to avoid FOIT.
 97
 98        `preload`:
 99            If `True`, also create a `<link rel="preload" as="font" ...>` for `href`.
100            Preload only warms the fetch; the @font-face rule still does the actual use.
101
102        `crossorigin`:
103            CORS mode for cross-origin fonts. When unset, it is
104            fetched as same-origin Use `""` or `anonymous` for most CDN/remote
105            cases, or `"use-credentials"` to pass cookies if strictly necessary.
106
107        `unicode_range`:
108            Optional CSS `unicode-range` (e.g., `"U+0000-00FF"`) to subset coverage.
109        """
110        if isinstance(hrefs, str):
111            hrefs = [hrefs]
112        self.hrefs = hrefs
113        self.weight = weight
114        self.style = style
115        self.display = display
116        self.preload = preload
117        self.crossorigin = crossorigin
118        self.cache_bust = cache_bust
119        self.has_link = preload
120        self.unicode_range = unicode_range
121        self._hrefs = self.uris()
122
123        # Escape most stuff that could break out of quotes
124        safe_family = (
125            family.replace("\\", "\\\\").replace("'", "\\'").replace('"', '\\"')
126        )
127        self.family = safe_family

Declare a web font via @font-face and (optionally) emit a preload <link>. This initializer targets the direct-file flow (you supply the exact .woff2 URL).

If multiple hrefs are provided, preload must choose only one and so the first href is used for preload and the rest are only in the @font-face.

Values are passed verbatim. NEVER use this with untrusted user input.

Parameters

hrefs: 1 or more URL to the font file (typically .woff2). Pass a str for a single URL, or an iterable of str for multiple URLs.

The @font-face `src` will reference this URL verbatim.

family: CSS font-family name to expose (e.g., "Noto Sans"). Do not include quotes; they are added automatically.

weight: Single numeric weight (e.g., 400) for a static face, or a (low, high) tuple (e.g., (100, 900)) for a variable font range.

style: Font style for this face: "normal" or "italic". Declare another instance for the other style if needed.

display: font-display strategy. "swap" is a safe default to avoid FOIT.

preload: If True, also create a <link rel="preload" as="font" ...> for href. Preload only warms the fetch; the @font-face rule still does the actual use.

crossorigin: CORS mode for cross-origin fonts. When unset, it is fetched as same-origin Use "" or anonymous for most CDN/remote cases, or "use-credentials" to pass cookies if strictly necessary.

unicode_range: Optional CSS unicode-range (e.g., "U+0000-00FF") to subset coverage.

hrefs
weight
style
display
preload
crossorigin
cache_bust
unicode_range
family
def uris(self) -> list[str]:
129    def uris(self) -> list[str]:  # -> list[str] | list[Any]:
130        """
131        Returns the source URI - with cache busting if enabled
132        which is implemented by getting the mtime of the local file
133
134        This operation performs up to two fs stats per call
135
136        1. The base static directory is checked once and cached per interpreter
137        2. The specific resource is checked if it is time to poll again
138           based on settings.stat_poll_interval
139        """
140        hrefs = []
141        for h in self.hrefs:
142            if not self.cache_bust:
143                hrefs.append(h)
144                continue
145
146            hrefs.append(_cachebust_resource_uri(h))
147        return hrefs

Returns the source URI - with cache busting if enabled which is implemented by getting the mtime of the local file

This operation performs up to two fs stats per call

  1. The base static directory is checked once and cached per interpreter
  2. The specific resource is checked if it is time to poll again based on settings.stat_poll_interval
@staticmethod
def get_font_format(href: str) -> str | None:
149    @staticmethod
150    def get_font_format(href: str) -> str | None:
151        ext = href.split(".")[-1].lower()
152        return _AUTODETECT_CSS_FONT_TYPES.get(ext, None)
@staticmethod
def get_font_mime(href: str) -> str | None:
154    @staticmethod
155    def get_font_mime(href: str) -> str | None:
156        ext = href.split(".")[-1].lower()
157        return _AUTODETECT_FONT_MIMETYPES.get(ext, None)
def style_data(self) -> str:
182    def style_data(self) -> str:
183        """
184        Returns one or more nodes for this import
185        """
186
187        font_refs = []
188
189        if len(self._hrefs) == 1:
190            # we don't have to hint format if there's only one
191            font_refs.append(f"url('{self._hrefs[0]}')")
192        else:
193            for h in self._hrefs:
194                entry = f"url('{h}')"
195                format = font_import_manual.get_font_format(h)
196                if format:
197                    entry += f" format('{format}')"
198
199                font_refs.append(entry)
200
201        if isinstance(self.weight, tuple):
202            font_weight = f"{self.weight[0]} {self.weight[1]}"
203        elif isinstance(self.weight, str):
204            font_weight = self.weight
205        elif isinstance(self.weight, int):
206            font_weight = str(self.weight)
207        else:
208            raise TypeError("font weight must be int, str, or tuple")
209
210        style_attrs = {
211            "font-family": f"'{self.family}'",
212            "src": ",\n\t\t".join(font_refs),
213            "font-style": self.style,
214            "font-display": self.display,
215            "font-weight": font_weight,
216            "unicode-range": self.unicode_range,
217        }
218        for k in list(style_attrs.keys()):
219            v = style_attrs[k]
220            if v is None:
221                del style_attrs[k]
222
223        return "\n".join(
224            [
225                "@font-face {",
226                "".join(
227                    [
228                        "\t",
229                        ";\n\t".join(
230                            [f"{k}: {v}" for k, v in style_attrs.items()]
231                        ),
232                        ";",  # Ensure last property ends with semicolon
233                    ]
234                ),
235                "}",
236            ]
237        )

Returns one or more nodes for this import

class font_import_provider(html_compose.resource.font_import._font_import_base):
243class font_import_provider(_font_import_base):
244    def __init__(
245        self,
246        href: str,
247        preload: bool = False,
248        hash: str | None = None,
249        crossorigin: Literal["", "anonymous", "user-credentials"]
250        | str
251        | None = None,
252        cache_bust: bool = False,
253        preconnect: list[str] | None = None,
254        preconnect_crossorigin: Literal["", "anonymous", "user-credentials"]
255        | None = None,
256    ):
257        """
258        A font import helper class for css based imports which employs:
259        - `link(rel="stylesheet")` to define the import
260        - `link(rel="preload")` to preload the import
261        - `link(rel="preconnect")` to preconnect to font providers
262        - `hash` and `crossorigin` for SRI
263        - Local resource cache busting
264
265        NEVER use this with untrusted user input.
266
267        Parameters
268        ----------
269        `href`:
270            The literal href of the CSS resource which sets up the font
271
272        `preload`:
273            If true, adds a preload link for this resource
274
275        `hash`:
276            An optional SRI integrity hash for the import
277
278        `crossorigin`:
279            Optionally sets the crossorigin attribute on the link tag.
280            Valid values are "", "anonymous", "use-credentials"
281
282        `preconnect`:
283            A list of URLs to preconnect to. This is useful for font providers
284            such as Google Fonts.
285
286        `preconnect_crossorigin`:
287            Optionally sets the crossorigin attribute on the preconnect link tag.
288            If not set, it defaults to the value of `crossorigin`.
289
290        `cache_bust`:
291            If true, appends a timestamp to the URL to prevent browser
292            caching. Webservers configured for static resources manage this
293            feature automatically, but for development this can be useful.
294
295            This feature only works for local resources, i.e. those that
296            exist in `resource.settings.base_dir`
297
298        """
299        self.href = href
300        self.preload = preload
301        self.hash = hash
302        self.crossorigin = crossorigin
303        self.cache_bust = cache_bust
304        self.has_link = preload
305        self._href = self.uri()
306        self.preconnect = preconnect
307
308        if hash and self.crossorigin is None:
309            raise ValueError(
310                "If hash is set, crossorigin must be set to ''/'anonymous'"
311            )
312        self.preconnect_crossorigin = preconnect_crossorigin
313        if preconnect_crossorigin is None and self.crossorigin:
314            if self.crossorigin != "user-credentials":
315                self.preconnect_crossorigin = self.crossorigin  # type: ignore[assignment]
316            else:
317                raise ValueError(
318                    "Please set preconnect_crossorigin explicitly if "
319                    "crossorigin is 'user-credentials'"
320                )
321
322    def uri(self):
323        """
324        Returns the source URI - with cache busting if enabled
325        which is implemented by getting the mtime of the local file
326
327        This operation performs up to two fs stats per call
328
329        1. The base static directory is checked once and cached per interpreter
330        2. The specific resource is checked if it is time to poll again
331           based on settings.stat_poll_interval
332        """
333        if not self.cache_bust:
334            return self.href
335
336        return _cachebust_resource_uri(self.href)
337
338    def preload_links(
339        self,
340    ) -> list[el.link]:  # -> list[Any]:# -> list[Any]:# -> list[Any]:
341        """
342        Returns preload links if preload is set
343        """
344
345        links = []
346
347        if self.preload:
348            attrs = {"href": self._href}
349            if self.hash:
350                attrs["integrity"] = self.hash
351            if self.crossorigin:
352                attrs["crossorigin"] = self.crossorigin
353            links.append(el.link(attrs=attrs, rel="preload", as_="style"))
354
355        return links
356
357    def links(self) -> list[el.link]:
358        """
359        Returns link elements for this import
360        """
361        links = []
362
363        attrs = {"href": self._href}
364        if self.hash:
365            attrs["integrity"] = self.hash
366        if self.crossorigin:
367            attrs["crossorigin"] = self.crossorigin
368
369        links.append(el.link(attrs=attrs, rel="stylesheet"))
370
371        return links
372
373    def style_data(self) -> str:
374        # Fonts imported via CSS do not have style nodes
375        return ""
376
377    def preconnect_links(self) -> list[el.link]:
378        """
379        Returns generated preconnect link elements for this import
380        """
381        links = []
382        if self.preconnect:
383            for pc in self.preconnect:
384                pc_attrs = {"href": pc}
385                if self.preconnect_crossorigin:
386                    pc_attrs["crossorigin"] = self.preconnect_crossorigin
387                links.append(el.link(attrs=pc_attrs, rel="preconnect"))
388        return links
font_import_provider( href: str, preload: bool = False, hash: str | None = None, crossorigin: Union[Literal['', 'anonymous', 'user-credentials'], str, NoneType] = None, cache_bust: bool = False, preconnect: list[str] | None = None, preconnect_crossorigin: Optional[Literal['', 'anonymous', 'user-credentials']] = None)
244    def __init__(
245        self,
246        href: str,
247        preload: bool = False,
248        hash: str | None = None,
249        crossorigin: Literal["", "anonymous", "user-credentials"]
250        | str
251        | None = None,
252        cache_bust: bool = False,
253        preconnect: list[str] | None = None,
254        preconnect_crossorigin: Literal["", "anonymous", "user-credentials"]
255        | None = None,
256    ):
257        """
258        A font import helper class for css based imports which employs:
259        - `link(rel="stylesheet")` to define the import
260        - `link(rel="preload")` to preload the import
261        - `link(rel="preconnect")` to preconnect to font providers
262        - `hash` and `crossorigin` for SRI
263        - Local resource cache busting
264
265        NEVER use this with untrusted user input.
266
267        Parameters
268        ----------
269        `href`:
270            The literal href of the CSS resource which sets up the font
271
272        `preload`:
273            If true, adds a preload link for this resource
274
275        `hash`:
276            An optional SRI integrity hash for the import
277
278        `crossorigin`:
279            Optionally sets the crossorigin attribute on the link tag.
280            Valid values are "", "anonymous", "use-credentials"
281
282        `preconnect`:
283            A list of URLs to preconnect to. This is useful for font providers
284            such as Google Fonts.
285
286        `preconnect_crossorigin`:
287            Optionally sets the crossorigin attribute on the preconnect link tag.
288            If not set, it defaults to the value of `crossorigin`.
289
290        `cache_bust`:
291            If true, appends a timestamp to the URL to prevent browser
292            caching. Webservers configured for static resources manage this
293            feature automatically, but for development this can be useful.
294
295            This feature only works for local resources, i.e. those that
296            exist in `resource.settings.base_dir`
297
298        """
299        self.href = href
300        self.preload = preload
301        self.hash = hash
302        self.crossorigin = crossorigin
303        self.cache_bust = cache_bust
304        self.has_link = preload
305        self._href = self.uri()
306        self.preconnect = preconnect
307
308        if hash and self.crossorigin is None:
309            raise ValueError(
310                "If hash is set, crossorigin must be set to ''/'anonymous'"
311            )
312        self.preconnect_crossorigin = preconnect_crossorigin
313        if preconnect_crossorigin is None and self.crossorigin:
314            if self.crossorigin != "user-credentials":
315                self.preconnect_crossorigin = self.crossorigin  # type: ignore[assignment]
316            else:
317                raise ValueError(
318                    "Please set preconnect_crossorigin explicitly if "
319                    "crossorigin is 'user-credentials'"
320                )

A font import helper class for css based imports which employs:

  • link(rel="stylesheet") to define the import
  • link(rel="preload") to preload the import
  • link(rel="preconnect") to preconnect to font providers
  • hash and crossorigin for SRI
  • Local resource cache busting

NEVER use this with untrusted user input.

Parameters

href: The literal href of the CSS resource which sets up the font

preload: If true, adds a preload link for this resource

hash: An optional SRI integrity hash for the import

crossorigin: Optionally sets the crossorigin attribute on the link tag. Valid values are "", "anonymous", "use-credentials"

preconnect: A list of URLs to preconnect to. This is useful for font providers such as Google Fonts.

preconnect_crossorigin: Optionally sets the crossorigin attribute on the preconnect link tag. If not set, it defaults to the value of crossorigin.

cache_bust: If true, appends a timestamp to the URL to prevent browser caching. Webservers configured for static resources manage this feature automatically, but for development this can be useful.

This feature only works for local resources, i.e. those that
exist in `resource.settings.base_dir`
href
preload
hash
crossorigin
cache_bust
preconnect
preconnect_crossorigin
def uri(self):
322    def uri(self):
323        """
324        Returns the source URI - with cache busting if enabled
325        which is implemented by getting the mtime of the local file
326
327        This operation performs up to two fs stats per call
328
329        1. The base static directory is checked once and cached per interpreter
330        2. The specific resource is checked if it is time to poll again
331           based on settings.stat_poll_interval
332        """
333        if not self.cache_bust:
334            return self.href
335
336        return _cachebust_resource_uri(self.href)

Returns the source URI - with cache busting if enabled which is implemented by getting the mtime of the local file

This operation performs up to two fs stats per call

  1. The base static directory is checked once and cached per interpreter
  2. The specific resource is checked if it is time to poll again based on settings.stat_poll_interval
def style_data(self) -> str:
373    def style_data(self) -> str:
374        # Fonts imported via CSS do not have style nodes
375        return ""
def to_elements( js: Optional[Iterable[str | js_import]] = None, css: Optional[Iterable[str | css_import]] = None, fonts: Optional[Iterable[font_import_manual | font_import_provider]] = None):
 62def to_elements(
 63    js: Iterable[str | js_import] | None = None,
 64    css: Iterable[str | css_import] | None = None,
 65    fonts: Iterable[font_import_manual | font_import_provider] | None = None,
 66):
 67    """
 68    Generate elements for `head` element from resource imports
 69
 70    Depending on your use case consider caching the resolution of this function
 71
 72
 73    :param js: Javascript imports. A string is treated as a simple script src
 74    :param css: CSS imports. A string is treated as a simple link rel=stylesheet
 75    :param fonts: Font imports
 76    """
 77    head_elements: list[base_types.Node] = []
 78
 79    preconnect_links = []
 80    preload_links = []
 81    links = []
 82    if css:
 83        for css_resource in css:
 84            if isinstance(css_resource, css_import):
 85                for link in css_resource.links():
 86                    links.append(link)
 87                for preload in css_resource.preloads():
 88                    preload_links.append(preload)
 89            else:
 90                if not isinstance(css_resource, str):
 91                    raise TypeError("css must be str or css_import")
 92
 93                links.append(el.link(rel="stylesheet", href=css_resource))
 94
 95    script_tags = []
 96    if js:
 97        #  <link rel="modulepreload" href="main.js" />
 98        js_imports: dict[str, str] = {}
 99        scopes: dict[str, dict[str, str]] = {}
100        for js_src in js:
101            if isinstance(js_src, js_import):
102                jsi: js_import = js_src
103                # Generate script tag
104                script_tags.append(jsi.script())
105                link = jsi.preload_link()
106                if link:
107                    preload_links.append(link)
108
109                entry = jsi.import_map_entry()
110                if entry:
111                    # Add to import map
112                    name, src = entry.name, entry.src
113                    js_imports[name] = src
114                    if entry.scope_urls:
115                        for scope_key in entry.scope_urls:
116                            sdict = scopes.setdefault(scope_key, {})
117                            sdict[name] = src
118
119            else:
120                if not isinstance(js_src, str):
121                    raise TypeError("js must be str or js_import")
122                script_tags.append(el.script(src=js_src))
123
124        if js_imports:
125            import_map: dict[str, Any] = {"imports": js_imports}
126            if scopes:
127                import_map["scopes"] = scopes
128            # dump to json; prevent escapes
129            js_dump = json.dumps(import_map).replace("<", "\\u003c")
130            preload_links.insert(
131                0, el.script(type="importmap")[unsafe_text(js_dump)]
132            )
133    style_text: list[str] = []
134    if fonts:
135        for font in fonts:
136            # Each font may be a different kind but the interface means
137            # we capture anything they generate
138            links.extend(font.links())
139            preconnect_links.extend(font.preconnect_links())
140            preload_links.extend(font.preload_links())
141            style_data = font.style_data()
142            if style_data:
143                style_text.append(style_data)
144
145    if preconnect_links:
146        head_elements.extend(preconnect_links)
147
148    if preload_links:
149        head_elements.extend(preload_links)
150
151    if links:
152        head_elements.extend(links)
153
154    if style_text:
155        head_elements.append(el.style()[unsafe_text("\n".join(style_text))])
156
157    if script_tags:
158        head_elements.extend(script_tags)
159
160    return head_elements

Generate elements for head element from resource imports

Depending on your use case consider caching the resolution of this function

Parameters
  • js: Javascript imports. A string is treated as a simple script src
  • css: CSS imports. A string is treated as a simple link rel=stylesheet
  • fonts: Font imports
class settings:
30class settings:
31    """
32    Global settings for js_import/css_import behavior.
33
34    `base_dir` is base directory from which local static files are served
35    and is used to construct cache busting URLs.
36    """
37
38    # Base directory when resolving relative paths for local resources
39    base_dir = "."
40    # html-compose cache-buster timestamp
41    query_string = "hccbts"
42    # Maximum number of cached URIs for cache busting
43    cache_cap = 1000
44    stat_poll_interval: int | float = 1  # seconds

Global settings for js_import/css_import behavior.

base_dir is base directory from which local static files are served and is used to construct cache busting URLs.

base_dir = '.'
query_string = 'hccbts'
cache_cap = 1000
stat_poll_interval: int | float = 1