-
Notifications
You must be signed in to change notification settings - Fork 1
Home
"Hoja de HyperTe" β A Python server-side rendering framework with selective Python-to-JavaScript compilation.
HTeaLeaf is an opinionated Python web framework. It has clear opinions about where state lives (the server), how the DOM updates (server-authoritative reconciliation), and where the Python/JS boundary sits (explicit, in your code).
HTeaLeaf lets you build full web applications in pure Python: components, state, interactivity and all. No context switching, no separate frontend build step, no state synchronization between client and server. When you need JavaScript, it's there: via transpiled Python, raw JS blocks, external files, or CDNs.
- One rendering model. Server-side rendering with surgical, opt-in client interactivity β no hydration mismatch, no bundle bloat by default.
- Reusable components. Components are plain Python functions. Share them across routes, compose them, pass arguments. it's just Python.
-
One state model.
Storefor module-level shared state,LocalState/use_state()for frontend state. -
Explicit Python/JS boundary. The
.jsnamespace on state objects makes the compilation boundary visible in your code.store.update(...)runs at render time (Python).store.js.update(...)compiles to JavaScript and runs in the browser.
Components as plain Python functions make HTeaLeaf code easy to read, write, and generate.
HTeaLeaf renders routes server-side and injects only the JavaScript that your @js-decorated functions require. There is no default client-side framework β the browser receives HTML with targeted interactivity attached.
| Primitive | Scope | Lives in |
|---|---|---|
Store() |
Module-level, shared across requests | Module global |
use_state() / LocalState
|
Route-scoped, per-render | Handler function |
The @js decorator marks a Python function for transpilation to JavaScript. HTeaLeaf's AST transformer rewrites Python variable names to their stable JS identifiers before compilation, so you write Python and get correct JS.
from htealeaf import use_state
from htealeaf.js import js
@app.route("/")
async def index():
counter = use_state(0) # must be created inside the handler
@js
def increment():
counter.set(counter.get() + 1)
...State objects expose a .js property returning a JSCode. This makes the execution boundary explicit:
# Runs server-side at render time:
store.update(new_value)
# Compiles to JavaScript, runs in the browser:
store.js.update(new_value)
store.js.delete()RenderContext (backed by contextvars.ContextVar, so it is safe under both threads and async) automatically registers @js functions and use_state() initializers during route execution. You don't manage <script> tags manually β the HTML renderer collects everything registered in the context and places the resulting <script> tags in <head> for you.
This works in both sync and async handlers β the render context is propagated to the executor thread that runs sync handlers.
htealeaf/
ββ elements/
β ββ elements # HTML elements like h1, button ...
β ββ component # Core HTML component class
β ββ renderer/ # HTML rendering + render context
ββ server/
β ββ server # Server core logic
β ββ session # Session management
β ββ http/ # Request / Response / Header
β ββ adapter/
β ββ wsgi # WSGI adapter
β ββ asgi # ASGI adapter
β ββ cgi # CGI adapter
ββ js/
β ββ jscode # JSCode, JSFunction, @js decorator
β ββ py2js # Python β JS transpiler
β ββ common # Ready-made JSCode helpers (document, console, ...)
ββ state/
ββ store # Store, AuthStore, SuperStore
ββ local_state # use_state() / LocalState
β οΈ This wiki documents thedevelopbranch. The current PyPI release (0.3.4) still ships the older CamelCase package (HTeaLeaf.Elements,HTeaLeaf.Server, β¦) and the examples in this wiki will not run against it. Until the next release, install from the development branch:
pip install "git+https://github.com/Az107/HTeaLeaf@develop"from htealeaf import HteaLeaf, adapters, use_state
from htealeaf.js import js
from htealeaf.elements import div, button, h1
app = HteaLeaf(adapters.ASGI)
@app.route("/")
async def index():
count = use_state(0)
@js
def increment():
count.set(count.get() + 1)
return div(
h1("Count: ", count),
button("Click me").attr(onclick=increment()),
)Suggested reading order:
- Getting Started β install, minimal app, run it
- Routing β defining routes and handlers
- Declarative HTML Elements β building HTML from Python components
-
State management
- Store: server side state
- Session: per-user server sessions
- LocalState: client side state
- JSCode and JS from Python β writing client-side logic in Python
| Project | Language | Role |
|---|---|---|
| HTeaPot | Rust | HTTP server |
| HTeaLeaf | Python | SSR framework (this project) |
| Cafetera | Rust | API mocker |
π HTeaLeaf
State
Client-side