Core Concepts¶
This guide explains the fundamental concepts of tdom-path and how the three core functions work together to provide component asset path resolution for web applications.
Overview¶
tdom-path provides a three-phase pipeline for resolving component assets:
Path Resolution - Convert asset references to Traversable instances
Tree Transformation - Automatically rewrite Node trees to use Traversable
Path Rendering - Convert Traversable to relative path strings for HTML output
The Three Core Functions¶
make_traversable() - Path Resolution¶
make_traversable() creates Traversable instances from asset path strings. It supports two path formats:
Package Paths (format: package:resource/path):
>>> from tdom_path import make_traversable
>>> # Reference asset from installed package
>>> css_path = make_traversable(None, "bootstrap:dist/css/bootstrap.css")
Relative Paths (format: resource/path, ./resource/path, or ../resource/path):
>>> from mysite.components.heading import Heading
>>> # Reference asset relative to component's module
>>> css_path = make_traversable(Heading, "static/styles.css")
Path type detection is automatic based on the presence of a colon (:) character.
make_path_nodes() - Tree Transformation¶
make_path_nodes() walks a Node tree and automatically transforms <link href="..."> and <script src="..."> elements to use Traversable instances:
>>> from tdom import Element
>>> from tdom_path import make_path_nodes
>>> from mysite.components.heading import Heading
>>> # Original tree with string asset references
>>> tree = Element("head", children=[
... Element("link", {"rel": "stylesheet", "href": "static/styles.css"}),
... Element("script", {"src": "static/script.js"}),
... ])
>>> # Transform to use Traversable
>>> transformed = make_path_nodes(tree, Heading)
>>> # Link href and script src are now Traversable instances
External URLs (http://, https://), special schemes (mailto:, tel:), and anchor-only links (#…) are left unchanged.
render_path_nodes() - Path Rendering¶
render_path_nodes() converts Traversable instances in a tree to relative path strings suitable for HTML output:
>>> from pathlib import PurePosixPath
>>> from tdom_path import render_path_nodes
>>> from tdom_path.tree import RelativePathStrategy
>>> # Render for a specific target page
>>> target = PurePosixPath("mysite/pages/about.html")
>>> rendered = render_path_nodes(transformed, target)
>>> # Paths are now relative strings: "../components/heading/static/styles.css"
>>> # With site prefix for subdirectory deployment
>>> strategy = RelativePathStrategy(site_prefix=PurePosixPath("mysite/static"))
>>> rendered = render_path_nodes(transformed, target, strategy=strategy)
Path Rewriting Lifecycle¶
This diagram shows the complete lifecycle from component definition to rendered HTML:
flowchart TD
A[Component with Asset References] --> B[make_path_nodes]
B --> C[Tree with Traversable Instances]
C --> D[render_path_nodes]
D --> E[Tree with Relative Path Strings]
E --> F[HTML Output]
style A fill:#e1f5ff
style C fill:#fff4e1
style E fill:#e7f5e1
style F fill:#ffe1e1
Function Relationships and Data Flow¶
This diagram illustrates how data flows between the three core functions:
flowchart LR
subgraph Input
STR[String Paths<br/>static/styles.css]
end
subgraph Phase1[Phase 1: Path Resolution]
MP[make_path]
STR --> MP
MP --> TRAV[Traversable Instance]
end
subgraph Phase2[Phase 2: Tree Transformation]
MPN[make_path_nodes]
TREE1[Node Tree<br/>with String Paths] --> MPN
MPN --> TREE2[Node Tree<br/>with Traversable]
TRAV -.used by.-> MPN
end
subgraph Phase3[Phase 3: Path Rendering]
RPN[render_path_nodes]
TREE2 --> RPN
TARGET[Target Path<br/>pages/about.html] --> RPN
RPN --> TREE3[Node Tree<br/>with Relative Paths]
end
subgraph Output
TREE3 --> HTML[HTML String<br/>../static/styles.css]
end
style STR fill:#e1f5ff
style TRAV fill:#fff4e1
style TREE2 fill:#fff4e1
style TREE3 fill:#e7f5e1
style HTML fill:#ffe1e1
Relative Path Calculation Mechanism¶
This diagram explains how relative paths are calculated from the target output location to the source asset:
flowchart TD
subgraph Inputs
SRC[Source Asset<br/>mysite/components/heading/static/styles.css]
TGT[Target Page<br/>mysite/pages/about.html]
end
subgraph Calculation
SRC --> SRCDIR[Extract Source Directory<br/>mysite/components/heading/static]
TGT --> TGTDIR[Extract Target Directory<br/>mysite/pages]
SRCDIR --> REL[Calculate Relative Path]
TGTDIR --> REL
REL --> RESULT[../components/heading/static/styles.css]
end
subgraph Strategy
PREFIX{Site Prefix?}
RESULT --> PREFIX
PREFIX -->|No| FINAL1[../components/heading/static/styles.css]
PREFIX -->|Yes| PREPEND[Prepend Site Prefix]
PREPEND --> FINAL2[mysite/static/components/heading/static/styles.css]
end
style SRC fill:#e1f5ff
style TGT fill:#e1f5ff
style RESULT fill:#fff4e1
style FINAL1 fill:#e7f5e1
style FINAL2 fill:#e7f5e1
Complete Pipeline Example¶
Here’s a full example showing all three phases:
>>> from pathlib import PurePosixPath
>>> from tdom import Element
>>> from tdom_path import make_path_nodes, render_path_nodes
>>> from tdom_path.tree import RelativePathStrategy
>>> from mysite.components.heading import Heading
>>> # Define component with mixed package and relative paths
>>> class PageHeading:
... def __html__(self):
... return Element("div", children=[
... Element("head", children=[
... # Package path - references bootstrap package
... Element("link", {
... "rel": "stylesheet",
... "href": "bootstrap:dist/css/bootstrap.css"
... }),
... # Relative path - references local component asset
... Element("link", {
... "rel": "stylesheet",
... "href": "static/heading.css"
... }),
... Element("script", {"src": "static/heading.js"}),
... ]),
... Element("h1", children=["Welcome"]),
... ])
>>> # Phase 1 & 2: Transform string paths to Traversable
>>> heading = PageHeading()
>>> tree = heading.__html__()
>>> path_tree = make_path_nodes(tree, Heading)
>>> # Bootstrap link href: Traversable for bootstrap package
>>> # Heading link href: Traversable for component's static/heading.css
>>> # Script src: Traversable for component's static/heading.js
>>> # Phase 3: Render for specific target page (relative paths)
>>> target = PurePosixPath("mysite/pages/about.html")
>>> rendered_tree = render_path_nodes(path_tree, target)
>>> # Paths are now relative strings calculated from target location
>>> # Convert to HTML string
>>> html_output = str(rendered_tree)
Decorator Pattern¶
For convenience, use the @path_nodes decorator to automatically apply make_path_nodes():
>>> from tdom_path import path_nodes
>>> class MyHeading:
... @path_nodes
... def __html__(self):
... return Element("link", {"href": "static/styles.css"})
>>> heading = MyHeading()
>>> tree = heading.__html__() # Already has Traversable instances
Key Design Principles¶
Immutable Transformations - All functions return new trees, never mutate originals
Type Safety - Comprehensive type hints enable IDE autocomplete and type checking
Fail-Fast Validation - Asset existence checked immediately with clear error messages
External URL Detection - External URLs and special schemes left unchanged
Cross-Platform Consistency - PurePosixPath ensures consistent web paths on all platforms
Extensibility - RenderStrategy Protocol allows custom rendering strategies
Package-First - References as package specs are just as supported as local files
Asset Validation¶
All assets are validated automatically during tree transformation:
>>> # If asset doesn't exist, transformation fails immediately
>>> try:
... tree = Element("link", {"href": "static/missing.css"})
... make_path_nodes(tree, Heading)
... except FileNotFoundError as e:
... # Clear error with component and attribute context
... assert "missing.css" in str(e)
... # "Asset not found: 'missing.css' (attribute: 'href', component: 'Heading'...)"
Validation ensures build-time detection of broken asset references, preventing runtime errors in production.
Next Steps¶
Explore API Reference for detailed function documentation
See Cookbook Patterns for common use cases