tdom-path

Easily rewrite the static asset paths in your tdom-based markup. Works great for static-site generators. Works with package assets.

  • Wrap a component with a decorator

  • Decorator finds <link> and replaces with TraversableElement that has the path

  • Later, string rendering resolves path

Overview

tdom-path provides utilities for resolving component static assets (CSS, JS, images) in component-based web applications. It supports both package assets (using package:path syntax) and relative paths, returning Traversable objects that represent resource locations suitable for web rendering.

Key Use Cases:

  • Static site generators (SSG) with component-based architectures

  • Component libraries with embedded static assets

  • Framework-independent asset resolution

  • Package asset management for reusable components

Features

  • Package Asset Support: Reference assets from installed packages using package:asset/path syntax

  • Component Asset Resolution: Pass component classes/instances directly to resolve local assets

  • Tree Rewriting: Automatically transform <link> and <script> elements to use Traversable

  • Decorator Support: Use @path_nodes decorator for automatic tree transformation

  • Flexible Path Formats: Supports package paths, relative paths with ./ or ../, and plain paths

  • Cross-Platform: Traversable ensures consistent resource access across platforms

  • Type Safety: Comprehensive type hints with IDE autocomplete and type checking support

  • Free Threaded: Written and tested to be free-threading friendly

  • Asset Validation: Automatic validation that referenced assets exist (fail-fast with clear errors)

  • Simple API: Clean functions for both manual and automatic use cases

  • Framework Independence: Same components work in Flask, Django, FastAPI, Sphinx

Installation

$ uv add tdom-path

Or using pip:

$ pip install tdom-path

Requirements

  • Python 3.14+

  • tdom >= 0.1.13

Quick Start

Functional component with decorator and relative path:

from tdom import html, Node
from tdom_path import path_nodes


@path_nodes
def Head():
    return html(t"""
<head>
    <link rel="stylesheet" href="static/styles.css">
</head>
    """)

Functional component that manually applies (not decorator):

from tdom import html, Node
from tdom_path import make_path_nodes


def Head():
    return make_path_nodes(html(t"""
<head>
    <link rel="stylesheet" href="static/styles.css">
</head>
    """), Head)

Functional component with decorator and package asset path:

from tdom import html, Node
from tdom_path import path_nodes


@path_nodes
def Head():
    return html(t"""
<head>
    <link rel="stylesheet" href="mypackage:static/styles.css">
</head>
    """)

Class-based component:

from dataclasses import dataclass
from tdom import html, Node
from tdom_path import path_nodes


@dataclass
class Head:

    @path_nodes
    def __call__(self):
        return html(t"""
    <head>
        <link rel="stylesheet" href="static/styles.css">
    </head>
        """)

How Path Type Detection Works:

  • If the path contains a colon (:), it’s treated as a package path

  • Otherwise, it’s treated as a relative path (relative to the component’s module)

  • No need to specify the type explicitly - detection is automatic

Supported Relative Path Formats:

  • static/styles.css - Plain relative path

  • ./static/styles.css - Explicit current directory

  • ../shared/utils.css - Parent directory navigation

Complete Pipeline

Here’s a complete example showing the full pipeline from component to rendered HTML:

from pathlib import PurePosixPath
from tdom import html
from tdom_path import make_path_nodes, render_path_nodes
from tdom_path.tree import RelativePathStrategy


# 1. Define your component with mixed package and relative paths
class Heading:
    def __html__(self):
        return html(t'''
            <div class="heading">
                <link rel="stylesheet" href="bootstrap:dist/css/bootstrap.css">
                <link rel="stylesheet" href="static/heading.css">
                <script src="static/heading.js"></script>
                <h1>Welcome</h1>
            </div>
        ''')


# 2. Transform string paths to Traversable (package and component-relative)
heading = Heading()
tree = heading.__html__()
path_tree = make_path_nodes(tree, heading)
# Bootstrap link href is now: Traversable for bootstrap package
# Heading link href is now: Traversable for component's static/heading.css
# Script src is now: Traversable for component's static/heading.js

# 3. Render for a specific target page (relative paths)
target = PurePosixPath("mysite/pages/about.html")
rendered_tree = render_path_nodes(path_tree, target)
# Paths are now calculated relative to target location

# 4. Convert to HTML string
html_output = str(rendered_tree)

Next Steps