Cookbook Patterns¶
This guide provides practical patterns for common use cases with tdom-path.
Building Component Libraries¶
Component libraries benefit from colocated assets - keeping CSS, JS, and other resources alongside component code.
Component Structure¶
mysite/
components/
heading/
__init__.py
heading.py
static/
heading.css
heading.js
Component Implementation¶
>>> # mysite/components/heading/heading.py
>>> from tdom import Element
>>> from tdom_path import path_nodes
>>> class Heading:
... def __init__(self, text: str):
... self.text = text
...
... @path_nodes
... def __html__(self) -> Element:
... return Element("div", {"class": "heading"}, children=[
... Element("head", children=[
... Element("link", {
... "rel": "stylesheet",
... "href": "static/heading.css"
... }),
... Element("script", {"src": "static/heading.js"}),
... ]),
... Element("h1", children=[self.text]),
... ])
Package Entry Point¶
>>> # mysite/components/heading/__init__.py
>>> from mysite.components.heading.heading import Heading
>>> __all__ = ["Heading"]
Usage¶
>>> from mysite.components.heading import Heading
>>> # Component automatically resolves its own assets
>>> heading = Heading("Welcome")
>>> tree = heading.__html__()
>>> # Assets are resolved relative to the heading module
Benefits¶
Colocated Assets - CSS and JS live with component code
Portable Components - Components work anywhere they’re imported
Type Safety - Asset paths validated at build time
Framework Independence - No framework-specific helpers needed
Creating Portable Themes¶
Themes package reusable styling and assets that work across different frameworks.
Theme Structure¶
mytheme/
__init__.py
base.py
static/
css/
base.css
components.css
js/
theme.js
components/
layout.py
navigation.py
Theme Base Component¶
>>> # mytheme/base.py
>>> from tdom import Element
>>> from tdom_path import path_nodes
>>> class ThemeBase:
... @path_nodes
... def __html__(self) -> Element:
... return Element("head", children=[
... # Theme assets using package paths
... Element("link", {
... "rel": "stylesheet",
... "href": "mytheme:static/css/base.css"
... }),
... Element("link", {
... "rel": "stylesheet",
... "href": "mytheme:static/css/components.css"
... }),
... Element("script", {"src": "mytheme:static/js/theme.js"}),
... ])
Using the Theme¶
>>> from mytheme import ThemeBase
>>> from tdom_path import make_path_nodes, render_path_nodes
>>> from pathlib import PurePosixPath
>>> # Theme works in any web framework
>>> theme = ThemeBase()
>>> tree = theme.__html__()
>>> # Assets resolved via package paths
>>> target = PurePosixPath("templates/index.html")
>>> rendered = render_path_nodes(tree, target)
>>> html_output = str(rendered)
Benefits¶
Framework Independence - Same theme works in any web framework
Package Distribution - Distribute themes as Python packages
Asset Bundling - All theme assets packaged together
No Framework Lock-in - Switch frameworks without rewriting themes
SSG Integration Pattern¶
Static Site Generators (SSGs) need to collect and copy all assets during build. tdom-path provides RelativePathStrategy.collected_assets for this use case.
Asset Collection During Build¶
>>> from pathlib import PurePosixPath, Path
>>> from tdom_path import make_path_nodes, render_path_nodes
>>> from tdom_path.tree import RelativePathStrategy
>>> # Create strategy to collect assets
>>> strategy = RelativePathStrategy()
>>> # Render multiple pages
>>> pages = [
... ("index.html", index_component),
... ("about.html", about_component),
... ("contact.html", contact_component),
... ]
>>> for filename, component in pages:
... tree = component.__html__()
... path_tree = make_path_nodes(tree, component)
...
... target = PurePosixPath(f"build/{filename}")
... rendered = render_path_nodes(path_tree, target, strategy=strategy)
...
... # Write rendered HTML
... Path(target).write_text(str(rendered))
>>> # After rendering all pages, copy collected assets
>>> build_dir = Path("build")
>>> for asset_ref in strategy.collected_assets:
... # asset_ref.source is Traversable for reading
... # asset_ref.module_path is PurePosixPath for destination
... dest_path = build_dir / asset_ref.module_path
... dest_path.parent.mkdir(parents=True, exist_ok=True)
... dest_path.write_bytes(asset_ref.source.read_bytes())
Benefits¶
Automatic Asset Discovery - No manual asset lists needed
Deduplication - AssetReference uses frozen dataclass for set-based dedup
Build-Time Copying - Copy assets only after all pages rendered
Complete Asset Manifest - collected_assets contains all referenced assets
Advanced Patterns¶
Using @path_nodes Decorator¶
The decorator automatically applies make_path_nodes() to component output:
>>> from tdom import Element
>>> from tdom_path import path_nodes
>>> # Function component
>>> @path_nodes
... def heading(text: str) -> Element:
... return Element("div", children=[
... Element("link", {"href": "static/styles.css"}),
... Element("h1", children=[text]),
... ])
>>> # Class component with __call__
>>> class HeadingCallable:
... @path_nodes
... def __call__(self) -> Element:
... return Element("link", {"href": "static/styles.css"})
>>> # Class component with __html__
>>> class HeadingHTML:
... @path_nodes
... def __html__(self) -> Element:
... return Element("link", {"href": "static/styles.css"})
Mixing Package and Relative Paths¶
Use both package paths and relative paths in the same component:
>>> class Component:
... @path_nodes
... def __html__(self) -> Element:
... return Element("head", children=[
... # Package path - from installed package
... Element("link", {
... "rel": "stylesheet",
... "href": "bootstrap:dist/css/bootstrap.css"
... }),
... # Relative path - from component's module
... Element("link", {
... "rel": "stylesheet",
... "href": "static/component.css"
... }),
... # Another package
... Element("script", {"src": "jquery:dist/jquery.min.js"}),
... # Local script
... Element("script", {"src": "static/component.js"}),
... ])
Validating Assets with Fail-Fast¶
Asset validation happens automatically during make_path_nodes():
>>> from mysite.components.heading import Heading
>>> from tdom_path import make_path_nodes
>>> try:
... tree = Element("link", {"href": "static/missing.css"})
... make_path_nodes(tree, Heading)
... except FileNotFoundError as e:
... # Error includes:
... # - Asset filename: 'missing.css'
... # - Attribute name: 'href'
... # - Component name: 'Component'
... # - Module context: 'mysite.components.component'
... # - Full path for debugging
... assert "missing.css" in str(e)
Benefits:
Build-time error detection
Clear error messages with context
Prevents broken links in production
Immediate feedback during development
Custom RenderStrategy for CDN¶
Implement custom rendering strategies for CDN or absolute URLs:
>>> from pathlib import PurePosixPath
>>> from importlib.resources.abc import Traversable
>>> from tdom_path.tree import RenderStrategy
>>> class CDNStrategy:
... """Render all paths as CDN URLs."""
...
... def __init__(self, cdn_base: str):
... self.cdn_base = cdn_base.rstrip("/")
...
... def calculate_path(self, source: Traversable, target: PurePosixPath) -> str:
... # Convert Traversable to path string
... path_str = str(source)
... # Return CDN URL
... return f"{self.cdn_base}/{path_str}"
>>> # Use custom strategy
>>> cdn_strategy = CDNStrategy("https://cdn.example.com")
>>> rendered = render_path_nodes(path_tree, target, strategy=cdn_strategy)
>>> # Assets now point to: https://cdn.example.com/mysite/components/heading/static/styles.css
Absolute Path Strategy¶
>>> class AbsolutePathStrategy:
... """Render all paths as absolute URLs from site root."""
...
... def calculate_path(self, source: Traversable, target: PurePosixPath) -> str:
... # Always return path from site root
... return f"/{source}"
>>> # Use absolute path strategy
>>> absolute_strategy = AbsolutePathStrategy()
>>> rendered = render_path_nodes(path_tree, target, strategy=absolute_strategy)
>>> # Assets now point to: /mysite/components/heading/static/styles.css
Best Practices¶
Colocate Assets - Keep CSS/JS with component code
Use Package Paths - For third-party assets (bootstrap, jquery, etc.)
Use Relative Paths - For component-local assets
Validate Early - Let fail-fast validation catch errors during development
Collect Assets for SSG - Use RelativePathStrategy.collected_assets for build tools
Framework Independence - Write components once, use everywhere
Type Safety - Leverage type hints for IDE support and type checking
Next Steps¶
Review API Reference for detailed function signatures
See Core Concepts for deeper understanding of the lifecycle