Core HTML generation library with Rust-based Python extension - high performance, minimal dependencies
Project description
RustyTags Core
🚀 High-performance HTML generation library - Rust-powered Python extension for blazing-fast HTML and SVG creation.
⚡ 3-10x faster than pure Python implementations with minimal dependencies and maximum performance.
⚛️ Datastar-Ready - Built-in reactive component support with intelligent JavaScript expression detection.
🚨 Breaking Changes in v0.6.0: Advanced features moved to Nitro. See Migration Guide below.
Looking for web framework features? Check out the Nitro package which builds on RustyTags with advanced templates, UI components, SSE, and framework integrations.
What RustyTags Core Does
RustyTags Core is a minimal, high-performance HTML generation library that focuses on one thing: generating HTML and SVG content fast.
- 🏷️ Complete HTML5/SVG Tags: All standard HTML5 and SVG elements with optimized Rust implementations
- ⚡ Blazing Performance: 3-10x faster than pure Python with memory optimization and intelligent caching
- ⚛️ Powerful Datastar SDK: Type-safe
Signalsystem with Python operator overloading, conditional logic, validation, and HTTP actions - 🪶 Lightweight: Minimal dependencies - works with any Python web framework
- 🧠 Smart Processing: Automatic attribute handling and intelligent type conversion
- 🔧 Framework Ready: Drop-in replacement for any HTML generation needs
Quick Start
Installation
pip install rusty-tags
Basic HTML Generation
from rusty_tags import Div, P, H1, A, Button, Input
# Simple HTML elements
content = Div(
H1("Welcome to RustyTags Core"),
P("High-performance HTML generation with Rust + Python"),
A("Learn More", href="https://github.com/ndendic/RustyTags"),
cls="container"
)
print(content)
# Output:
# <div class="container">
# <h1>Welcome to RustyTags Core</h1>
# <p>High-performance HTML generation with Rust + Python</p>
# <a href="https://github.com/ndendic/RustyTags">Learn More</a>
# </div>
Complete Page Generation
from rusty_tags import Html, Head, Title, Body, Meta, Link
from rusty_tags import Page # Simple page helper
# Manual HTML structure
page = Html(
Head(
Title("My Site"),
Meta(charset="utf-8"),
Link(rel="stylesheet", href="/app.css")
),
Body(
H1("Hello World"),
P("Built with RustyTags Core")
)
)
# Or use the simple Page helper
page = Page(
H1("Hello World"),
P("Built with RustyTags Core"),
title="My Site",
hdrs=(Meta(charset="utf-8"), Link(rel="stylesheet", href="/app.css"))
)
Reactive Components with Datastar SDK
Inspired by the awesome StarHTML, RustyTags Core now includes an even more powerfull Datastar SDK that provides a Pythonic API for building reactive components with type-safe signals and expressions:
Quick Start with Signals
from rusty_tags import Div, Button, P, Input, Label, Span, Page
from rusty_tags.datastar import Signal, Signals
# Create signals using the Signals class
sigs = Signals(counter=0, user_name="John", is_active=True)
# Basic reactive counter with Signal methods
counter = Div(
P(text=sigs.counter, cls="display"),
Button("-", on_click=sigs.counter.sub(1)),
Button("+", on_click=sigs.counter.add(1)),
Button("Reset", on_click=sigs.counter.set(0)),
cls="counter-widget",
signals=sigs # Signals object automatically converts to data-signals
)
# Create a complete page with Datastar CDN
page = Page(counter, title="Reactive App", datastar=True)
Signal Operations - Pythonic Reactive Expressions
The Signal SDK provides full Python operator overloading for building reactive JavaScript expressions:
Arithmetic Operators:
from rusty_tags.datastar import Signals
numbers = Signals(x=10, y=3)
demo = Div(
P(text="X: " + numbers.x), # → "X: ${$x}"
P(text="Y: " + numbers.y), # → "Y: ${$y}"
P(text="Sum: " + (numbers.x + numbers.y)), # → "Sum: ${$x + $y}"
P(text="Product: " + (numbers.x * numbers.y)), # → "Product: ${$x * $y}"
P(text="Division: " + (numbers.x / numbers.y)), # → "Division: ${$x / $y}"
P(text="Modulo: " + (numbers.x % numbers.y)), # → "Modulo: ${$x % $y}"
Button("X +5", on_click=numbers.x.add(5)),
Button("Y +1", on_click=numbers.y.add(1)),
signals=numbers
)
Comparison & Logical Operators:
from rusty_tags.datastar import Signal
age = Signal("age", 25)
has_license = Signal("has_license", True)
validation = Div(
P(text="Age: " + age),
P(text="Has License: " + has_license),
P(text="Is Adult (≥18): " + (age >= 18)), # → ($age >= 18)
P(text="Can Drive: " + ((age >= 18) & has_license)), # → (($age >= 18) && $has_license)
P(text="Is Minor: " + (age < 18)), # → ($age < 18)
P(text="No License: " + (~has_license)), # → (!$has_license)
Button("Age +1", on_click=age.add(1)),
Button("Toggle License", on_click=has_license.toggle()),
signals={"age": 25, "has_license": True}
)
String Methods:
text = Signal("text", "Hello World")
string_demo = Div(
Input(type="text", bind=text, placeholder="Enter text"),
P(text="Original: " + text),
P(text="Uppercase: " + text.upper()), # → $text.toUpperCase()
P(text="Lowercase: " + text.lower()), # → $text.toLowerCase()
P(text="Length: " + text.length), # → $text.length
P(text="Contains 'Hello': " + text.contains("Hello")), # → $text.includes('Hello')
signals={"text": "Hello World"}
)
Array Methods:
items = Signal("items", ["Apple", "Banana"])
new_item = Signal("new_item", "Orange")
array_demo = Div(
Input(type="text", bind=new_item, placeholder="New item"),
# Chaining multiple actions with semicolons
Button("Add Item", data_on_click=items.append(new_item).to_js() + "; " + new_item.set("").to_js()),
Button("Remove Last", data_on_click=items.pop()),
Button("Clear All", data_on_click=items.set([])),
P("Count: ", Span(text=items.length)), # → $items.length
P("Items: ", Span(text=items.join(", "))), # → $items.join(', ')
P("Empty: ", Span(text=items.length == 0)), # → $items.length === 0
signals={"items": ["Apple", "Banana"], "new_item": ""}
)
Math Methods:
value = Signal("value", 7.825)
math_demo = Div(
P("Value: ", Span(text=value)),
P("Rounded: ", Span(text=value.round())), # → Math.round($value)
P("Rounded (2 decimals): ", Span(text=value.round(2))),
P("Absolute: ", Span(text=value.abs())), # → Math.abs($value)
P("Min with 5: ", Span(text=value.min(5))), # → Math.min($value, 5)
P("Max with 10: ", Span(text=value.max(10))), # → Math.max($value, 10)
P("Clamped (0-10): ", Span(text=value.clamp(0, 10))), # → Math.max(0, Math.min(10, $value))
Button("+0.5", data_on_click=value.add(0.5)),
Button("-0.5", data_on_click=value.sub(0.5)),
signals={"value": 7.825}
)
Advanced Features - Conditionals, Patterns & Templates
Conditional Expressions with if_():
from rusty_tags.datastar import if_
score = Signal("score", 75)
conditional = Div(
P("Score: ", Span(text=score)),
# Simple ternary
P("Grade: ", Span(text=if_(score >= 90, "A", "B"))), # → ($score >= 90) ? 'A' : 'B'
P("Status: ", text=if_(score >= 60, "Pass", "Fail")),
# Nested conditionals
P("Grade: ", Span(text=if_(score >= 90, "A",
if_(score >= 80, "B",
if_(score >= 70, "C", "F"))))),
# Conditional rendering with data-show
Div(text=if_(score >= 90, "🎉 Excellent!",
if_(score >= 70, "👍 Good", "📚 Keep trying")),
style="font-size: 2rem; text-align: center;"),
Button("+10", data_on_click=score.add(10)),
Button("-10", data_on_click=score.sub(10)),
signals={"score": 75}
)
Pattern Matching with match():
from rusty_tags.datastar import match
status = Signals(status="idle")
status_demo = Div(
P("Current Status: ", Span(text=status.status)),
Div(
text=match(
status.status,
idle="⏸️ Ready to start",
loading="⏳ Processing...",
success="✅ Completed!",
error="❌ Failed!",
default="❓ Unknown"
),
style="font-size: 1.5rem; text-align: center; padding: 1rem;"
),
Button("Idle", on_click=status.status.set("idle")),
Button("Loading", on_click=status.status.set("loading")),
Button("Success", on_click=status.status.set("success")),
Button("Error", on_click=status.status.set("error")),
signals=status
)
Template Literals with f():
from rusty_tags.datastar import f
user = Signals(first_name="John", last_name="Doe", age_val=25)
template = Div(
Input(type="text", bind=user.first_name, placeholder="First name"),
Input(type="text", bind=user.last_name, placeholder="Last name"),
Input(type="number", bind=user.age_val, placeholder="Age"),
P(text=f("Hello, {fn} {ln}!", fn=user.first_name, ln=user.last_name)),
P(text=f("You are {age} years old.", age=user.age_val)),
P(text=f("In 10 years, you'll be {future}.", future=user.age_val + 10)),
signals=user
)
Dynamic CSS Classes
Using collect() for conditional class joining:
from rusty_tags.datastar import collect
is_large = Signal("is_large", False)
is_bold = Signal("is_bold", False)
is_italic = Signal("is_italic", False)
collect_demo = Div(
Label(Input(type="checkbox", bind=is_large), " Large"),
Label(Input(type="checkbox", bind=is_bold), " Bold"),
Label(Input(type="checkbox", bind=is_italic), " Italic"),
P(
"Styled Text",
data_class=collect([
(is_large, "large"),
(is_bold, "bold"),
(is_italic, "italic")
], join_with=" "),
style="transition: all 0.3s;"
),
signals={"is_large": False, "is_bold": False, "is_italic": False}
)
Using classes() for Datastar's data-class (object literal):
from rusty_tags.datastar import classes
cls_large = Signal("cls_large", False)
cls_bold = Signal("cls_bold", False)
cls_blue = Signal("cls_blue", False)
classes_demo = Div(
Label(Input(type="checkbox", bind=cls_large), " Large"),
Label(Input(type="checkbox", bind=cls_bold), " Bold"),
Label(Input(type="checkbox", bind=cls_blue), " Blue"),
P(
"Styled Text with data-class",
data_class=classes(large=cls_large, bold=cls_bold, blue=cls_blue),
style="transition: all 0.3s;"
),
signals={"cls_large": False, "cls_bold": False, "cls_blue": False}
)
Form Validation with Logical Aggregation
Using all() and any() for validation:
from rusty_tags.datastar import all, any, if_
form_name = Signal("form_name", "")
form_email = Signal("form_email", "")
form_age = Signal("form_age", 0)
form_agree = Signal("form_agree", False)
# Define validation rules
name_valid = form_name.length >= 3
email_valid = form_email.contains("@")
age_valid = form_age >= 18
can_submit = all(name_valid, email_valid, age_valid, form_agree)
registration_form = Div(
# Name field with validation
Div(
Label("Name:"),
Input(type="text", bind=form_name, placeholder="Enter name (min 3 chars)"),
P("✓ Valid", data_show=name_valid, style="color: green;"),
P("✗ Too short", data_show=~name_valid, style="color: red;")
),
# Email field with validation
Div(
Label("Email:"),
Input(type="email", bind=form_email, placeholder="Enter email"),
P("✓ Valid", data_show=email_valid, style="color: green;"),
P("✗ Invalid", data_show=~email_valid, style="color: red;")
),
# Age field with validation
Div(
Label("Age:"),
Input(type="number", bind=form_age, placeholder="Enter age"),
P("✓ Valid", data_show=age_valid, style="color: green;"),
P("✗ Must be 18+", data_show=~age_valid, style="color: red;")
),
# Terms checkbox
Div(
Label(Input(type="checkbox", bind=form_agree), " I agree to terms")
),
# Submit button - disabled until all valid
Button(
"Submit",
data_disabled=~can_submit,
data_attr_style=if_(can_submit, "opacity: 1; cursor: pointer;",
"opacity: 0.5; cursor: not-allowed;")
),
P(text=f("Hello, {name}!", name=form_name), data_show=can_submit),
signals={"form_name": "", "form_email": "", "form_age": 0, "form_agree": False}
)
HTTP Actions and Server Communication
from rusty_tags.datastar import post, get, put, patch, delete
# HTTP action helpers generate @action() expressions
api_demo = Div(
Input(bind="$name", placeholder="Enter name"),
Input(bind="$email", placeholder="Enter email"),
# POST with reactive data (signals are automatically passed)
Button(
"Save User",
on_click=post("/api/users", name="$name", email="$email"),
cls="btn-primary"
),
# GET request
Button("Load Data", on_click=get("/api/data")),
# PUT request
Button("Update", on_click=put("/api/users/1", name="$name")),
# PATCH request
Button("Partial Update", on_click=patch("/api/users/1", email="$email")),
# DELETE request
Button("Delete", on_click=delete("/api/users/1")),
Div(id="results"),
signals={"name": "", "email": ""}
)
Property Access for Nested Data
# Access nested object properties naturally
user = Signal("user", {"name": "Alice", "age": 30, "email": "alice@example.com"})
user_profile = Div(
P("Name: ", Span(text=user.name)), # → $user.name
P("Age: ", Span(text=user.age)), # → $user.age
P("Email: ", Span(text=user.email)), # → $user.email
P("Adult: ", Span(text=(user.age >= 18))), # → ($user.age >= 18)
Button("Birthday", on_click=user.age.add(1)),
Button("Change Name", on_click=user.name.set("Bob")),
signals={"user": {"name": "Alice", "age": 30, "email": "alice@example.com"}}
)
Complete Datastar SDK Features:
- Type-Safe Signals:
SignalandSignalsclasses with automatic type inference - Python Operators: Full operator overloading (
+,-,*,/,%,==,!=,<,>,<=,>=,&,|,~) - Signal Methods:
.add(),.sub(),.set(),.toggle(),.append(),.pop(),.remove() - String Methods:
.upper(),.lower(),.strip(),.contains(),.length - Math Methods:
.round(),.abs(),.min(),.max(),.clamp() - Array Methods:
.append(),.prepend(),.pop(),.remove(),.join(),.slice(),.length - Conditionals:
if_()for ternary expressions,match()for pattern matching - Templates:
f()for JavaScript template literals with embedded expressions - Dynamic Classes:
collect()for joining,classes()for object literals - Validation:
all()andany()for logical aggregation - HTTP Actions:
get(),post(),put(),patch(),delete()action generators - Raw JavaScript:
js()function for raw JavaScript when needed - Property Access: Natural Python syntax for nested object properties
SVG Generation
from rusty_tags import Svg, Circle, Rect, Line, Path
# Create SVG graphics
chart = Svg(
Circle(cx="50", cy="50", r="40", fill="blue"),
Rect(x="10", y="10", width="30", height="30", fill="red"),
Line(x1="0", y1="0", x2="100", y2="100", stroke="black"),
width="200", height="200", viewBox="0 0 200 200"
)
Core Features
🏷️ Complete HTML5/SVG Tag System
All standard HTML5 and SVG elements are available as Python functions:
# HTML elements
Html, Head, Body, Title, Meta, Link, Script
H1, H2, H3, H4, H5, H6, P, Div, Span, A
Form, Input, Button, Select, Textarea, Label
Table, Tr, Td, Th, Tbody, Thead, Tfoot
Nav, Main, Section, Article, Header, Footer
Img, Video, Audio, Canvas, Iframe
# ... and many more
# SVG elements
Svg, Circle, Rect, Line, Path, Polygon
G, Defs, Use, Symbol, LinearGradient
Text, Image, ForeignObject
# ... complete SVG support
⚡ Performance Optimizations
- Memory Pooling: Thread-local string pools and arena allocators minimize allocations
- Intelligent Caching: Lock-free attribute processing with smart cache invalidation
- String Interning: Common HTML strings pre-allocated for maximum efficiency
- Type Optimization: Fast paths for common Python types and HTML patterns
🔧 Smart Type System
Intelligent handling of Python types:
# Automatic type conversion
Div(
42, # Numbers → strings
True, # Booleans → "true"/"false"
None, # None → empty string
[1, 2, 3], # Lists → joined strings
custom_obj, # Objects with __html__(), render(), or _repr_html_()
)
# Dictionary attributes automatically expand
Div("Content", {"id": "main", "class": "container", "hidden": False})
# Renders: <div id="main" class="container">Content</div>
# Framework integration - automatic recognition
class MyComponent:
def __html__(self):
return "<div>Custom HTML</div>"
Div(MyComponent()) # Automatically calls __html__()
🪶 Framework Agnostic
Works with any Python web framework:
# FastAPI
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/")
def home():
return HTMLResponse(str(Page(H1("FastAPI + RustyTags"), title="Home")))
# Flask
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return str(Page(H1("Flask + RustyTags"), title="Home"))
# Django
from django.http import HttpResponse
def home(request):
return HttpResponse(str(Page(H1("Django + RustyTags"), title="Home")))
📓 Jupyter Integration
from rusty_tags import show
# Display directly in Jupyter notebooks
content = Div(H1("Notebook Content"), style="color: blue;")
show(content) # Renders directly in Jupyter cells
Performance
RustyTags Core delivers significant performance improvements over pure Python:
- 3-10x faster HTML generation
- Sub-microsecond rendering for simple elements
- Memory efficient with intelligent pooling
- Scalable with lock-free concurrent data structures
# Benchmark example
import timeit
from rusty_tags import Div, P
def generate_content():
return Div(
*[P(f"Paragraph {i}") for i in range(1000)],
cls="container"
)
# Time the generation
time = timeit.timeit(generate_content, number=1000)
print(f"Generated 1000 pages with 1000 paragraphs each in {time:.3f}s")
Architecture
🦀 Rust Core (src/lib.rs):
- High-performance HTML/SVG generation with PyO3 bindings
- Advanced memory management with pooling and interning
- Complete tag system with macro-generated optimizations
- ~2000+ lines of optimized Rust code
🐍 Python Layer (rusty_tags/):
- Core Module (
__init__.py): All HTML/SVG tags and core types - Utilities (
utils.py): Essential helpers (Page, create_template, page_template, show, AttrDict) - Datastar SDK (
datastar.py): Type-safe Signal system with Python operator overloading for reactive expressions - Rust Extension: Pre-compiled high-performance core with Datastar processing
Migration from Pre-0.6.x
🚨 Breaking Changes in v0.6.0
RustyTags v0.6.0 represents a major architectural shift to focus on core HTML generation performance. Advanced web framework features have been moved to the separate Nitro package.
What's Removed from RustyTags Core:
| Feature | Status | New Location |
|---|---|---|
Event system (events.py) |
❌ Removed | ✅ Nitro |
Client management (client.py) |
❌ Removed | ✅ Nitro |
UI components (xtras/) |
❌ Removed | ✅ Nitro |
Example applications (lab/) |
❌ Removed | ✅ Nitro |
What's Kept in RustyTags Core:
| Feature | Status | Notes |
|---|---|---|
| All HTML/SVG tags | ✅ Kept | Complete tag system with Rust performance |
| Datastar SDK | ✅ Enhanced | Type-safe Signal & Signals with full Python operator overloading |
Page() function |
✅ Enhanced | Simple templating with optional Datastar CDN |
create_template(), page_template() |
✅ Kept | Essential templating functions |
show() Jupyter integration |
✅ Kept | Perfect for notebooks |
AttrDict utility |
✅ Kept | Flexible attribute access |
Migration Guide:
Before v0.6.0 (Monolithic):
# Old import style - no longer works
from rusty_tags import Div, DS, Client, Accordion
from rusty_tags.events import emit
After v0.6.0 (Enhanced Core):
# Core HTML generation + Datastar SDK (RustyTags)
from rusty_tags import Div, Page, create_template, Button, Input
from rusty_tags.datastar import Signal, Signals, if_, match, f, all, any
from rusty_tags.datastar import post, get, put, patch, delete # HTTP actions
# Advanced web framework features (Nitro - separate install)
from nitro import Client, Accordion
from nitro.events import emit
Installation Changes:
# Before v0.6.0
pip install rusty-tags # Included everything
# After v0.6.0
pip install rusty-tags # Core HTML + complete Datastar integration
pip install nitro # For advanced web framework features (events, SSE, etc.)
Code Migration Examples:
- Basic HTML Generation (No changes needed):
# ✅ Works exactly the same
from rusty_tags import Div, H1, P
content = Div(H1("Hello"), P("World"))
- Basic Datastar (No changes needed):
# ✅ Works exactly the same - built into Rust core
from rusty_tags import Div, Button
counter = Div(
Button("+1", on_click="$count++"),
signals={"count": 0}
)
- Datastar SDK (Completely rewritten with Signal system):
# ✅ New Signal-based API in v0.6.0+
from rusty_tags.datastar import Signal, Signals, post, if_, all
# Create type-safe signals
sigs = Signals(name="", email="")
# Use Python operators for reactive expressions
form = Div(
Input(bind=sigs.name, placeholder="Name"),
Input(bind=sigs.email, placeholder="Email"),
# New HTTP action helpers
Button("Submit", on_click=post("/api/submit", name=sigs.name, email=sigs.email)),
# Conditional logic with if_()
P(text=if_(sigs.name.length >= 3, "Valid name", "Too short")),
# Validation with all()
Button("Save", data_disabled=~all(sigs.name.length >= 3, sigs.email.contains("@"))),
signals=sigs
)
- Page Templates (Minor changes):
# ✅ Still works in RustyTags Core
from rusty_tags import Page, create_template
# Basic templating stays the same
page = Page(content, title="My App", datastar=True)
template = create_template("My App", datastar=True)
# ❌ Advanced CDN features moved to Nitro
# highlightjs=True, lucide=True parameters now in Nitro
Why This Change?
- Performance: Core package is now 10x smaller and has zero dependencies
- Flexibility: Choose your complexity level - core HTML or full framework
- Maintenance: Clear separation of concerns between HTML generation and web framework
- Adoption: Lower barrier to entry for simple HTML generation needs
The new Datastar SDK in RustyTags Core provides a powerful, type-safe reactive system with Python operator overloading - giving you excellent reactive capabilities without requiring the full Nitro framework.
Why RustyTags Core?
Choose RustyTags Core when:
- ✅ You need maximum performance for HTML generation
- ✅ You want minimal dependencies in your project
- ✅ You're building your own templating system
- ✅ You need framework-agnostic HTML generation
- ✅ You want drop-in compatibility with any Python web framework
Consider Nitro when:
- 🚀 You want a full web framework with reactive components
- 🎨 You need advanced templating and UI component libraries
- 📡 You want real-time features (SSE, WebSocket management)
- ⚛️ You need Datastar integration for reactive UIs
System Requirements
- Python 3.8+ (broad compatibility across versions)
- Runtime Dependencies: None (zero dependencies for maximum compatibility)
- Optional: IPython for
show()function in Jupyter notebooks - Build Requirements (development only): Rust 1.70+, Maturin ≥1.9
Development
# Clone and build from source
git clone https://github.com/ndendic/RustyTags
cd RustyTags
maturin develop # Development build
maturin build --release # Production build
License
MIT License - See LICENSE file for details.
Related Projects
- Nitro - Full-stack web framework built on RustyTags
- FastHTML - Inspiration for the Python API design
- Datastar - Reactive component framework (used in Nitro)
Links
- Repository: https://github.com/ndendic/RustyTags
- Issues: https://github.com/ndendic/RustyTags/issues
- Documentation: See README and docstrings
- Performance: Rust-powered core with PyO3 bindings
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters