Pyrite
Code websites in Python 3, for JavaScript and CSS haters.
Pyrite is a small, dependency-free starting point: compose Python components, return a page, and serve HTML with built-in responsive styling. Requires Python 3.10+.
Run the example
python3 example.py
Open http://127.0.0.1:8000. The example is Pyrite's first website: a homepage
with a code/output comparison, quickstart, expandable FAQ, and a component
guide at /guide. Try /playground for a Python-powered calculator.
Its application source uses only Python components.
No installation is required when running from this repository.
Build a page
from pyrite import App, Column, Heading, Page, Text
app = App()
@app.page("/")
def home():
return Page(
Column(Heading("Hello, Pyrite."), Text("Just Python.")),
title="My website",
)
if __name__ == "__main__":
app.run()
Available components: Heading, Text, Column, Row, Card, Link, and
Button, Section, Nav, Code, and Details. Nest components directly.
Section(..., id="start") creates an anchor target. Code(source, label="Python")
displays escaped source. Details(question, ...) provides a native expandable answer.
Heading accepts level=1 through 6.
Link(label, href) and Button(label, href) navigate to another URL.
Text and HTML attributes are escaped automatically.
Register exact paths with @app.page("/about"). Handlers take no arguments and
return a Page. Query strings do not affect route matching. Page accepts
title and language; app.run accepts host and port.
Current scope
This version serves server-rendered pages with native GET forms and JSON POST
callbacks through LiveForm and Chat. It does not include server sessions,
token streaming, live reload, or a production server. The built-in
HTTP server is for local development. Styling is supplied by Pyrite; applications
do not need CSS files or JavaScript tooling.
Styling in Python
Edit the root styles.py to change the example website's shared theme. Restart
the server after editing. Theme supports six-digit hex colors, sans, serif,
or mono fonts, page width, corner radius, and spacing. Apply a theme to any page:
from pyrite import Theme
theme = Theme(background="#111820", text="#eef3f8", heading_font="sans")
page = Page(Heading("Your style."), theme=theme)
Forms without JavaScript
from pyrite import Form, Input, Submit, query
@app.page("/hello")
def hello():
return Page(Column(
Form(Input("name", "Your name", value=query("name")), Submit("Say hello")),
Text(f"Hello, {query('name', 'world')}!"),
))
Slider(name, label, value=5, minimum=1, maximum=10) and
Select(name, label, {"value": "Label"}, value="value") also work in forms.
Submission reloads the page; query reads request-local URL parameters. GET forms
are for read-only calculations and searches; their values appear in the URL.
Try Pyrite in Browser
Visit /try to edit Python and render a real Page without installing Python.
Assign your result to page; do not call app.run(). The editor loads Pyodide
from jsDelivr and runs code in a disposable Web Worker, with a 10-second execution
limit and an isolated preview. First download can take up to two minutes.
Code is not executed on the application server. Browser networking follows CORS
rules; the standard-library HTTP client below is for server-side Python.
Images and motion
from pyrite import Image, Motion
from styles import hero_image
banner = Motion(Image(hero_image, "Mountain landscape"), effect="rise")
Choose rise, fade, or float. In styles.py, set motion=False to disable
component animations, or change motion_duration (100–5000 milliseconds) for
entrances. Reduced-motion browser preferences disable animations automatically.
Theme(background_image="https://example.com/background.jpg") attaches a page
background. Image accepts relative or HTTP(S) URLs and requires alt text.
External images load directly from their host; local file serving is not included.
Requests and changing API data
from pyrite import requests
data = requests.get(
"https://api.mymemory.translated.net/get",
params={"q": "Good morning", "langpair": "en|uk"},
).json()
# For an API you control, with the authentication it requires:
# requests.post("https://your-api.example/items", json={"name": "New"})
# requests.put("https://your-api.example/items/1", json={"name": "Changed"})
# requests.delete("https://your-api.example/items/1")
The dependency-free client supports get, post, put, and delete, optional
headers, JSON bodies, query parameters, and a timeout (8 seconds by default).
Responses expose status_code, text, and json(). Catch requests.RequestError
for HTTP/network/decoding failures. Responses are limited to 1 MiB. Supply trusted
URLs in application code; do not forward visitor-provided URLs to this client.
GET page handlers should only read data. POST actions can make remote writes;
add application authentication and authorization before exposing private data or
operations. Pyrite's local development server does not provide authentication.
/translate demonstrates input, a real free MyMemory request, and updated page
content. No API key required; availability, translation quality, and quotas depend
on MyMemory. Submitted text is sent to that service.
Corners and gradients
from pyrite import Gradient, Theme
theme = Theme(
radius=20, # Panels, code blocks, images, details
button_radius=999, # Pill buttons; 0 gives square corners
input_radius=12, # Inputs and dropdowns
gradient=Gradient(
colors=("#101923", "#214a42", "#17283d"),
angle=120,
moving=True,
duration=14, # Seconds per cycle
),
)
button_radius and input_radius inherit radius when omitted. Gradients accept
2–8 hex colors; set moving=False for a static gradient or gradient=None for
the usual background. A fixed background layer animates independently of page
length. motion=False or the browser's reduced-motion preference stops its movement.
The gradient layer sits over a background image when both are configured.
In-page requests and chat
from pyrite import App, Chat, Page
app = App()
@app.action("/api/reply")
def reply(data):
message = data["message"]
history = data.get("history", [])
# Call a model here with pyrite.requests; keep API keys on the server.
return {"reply": "You said: " + message}
@app.page("/")
def home():
return Page(Chat(action="/api/reply"))
Visit /chat for a working local rule-based chatbot and a live translation form.
Chat sends {message, history} and displays reply without a page reload.
History stays in this page's browser memory (last 20 messages), is sent with each
request, and clears on reload. It is untrusted input; validate it in callbacks.
This is completed-response chat, not token streaming. The demo uses no AI service.
For other interfaces, use LiveForm(Input(...), Submit(...), action="/api/action")
and return {"text": "Updated result"} from the action. All field values arrive
as strings. Pyrite supplies the JavaScript, pending state, errors, 30-second
client timeout, and safe text rendering. The callback can use requests to reach
external APIs without exposing credentials to the browser. Aborting a request
does not cancel Python work already running.
Actions accept JSON objects up to 16 KiB and reject cross-origin browser requests. These checks do not authenticate users. Use application validation and authorization for sensitive actions. Live components require JavaScript; ordinary pages and GET forms continue working without it. Browser playground previews remain isolated and cannot call server actions.
Tests
python3 -m unittest discover -s tests -v
Metadata
Release files for pyrite-web 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyrite_web-0.1.0.tar.gz | 23.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyrite_web-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.9 kB
Release files / pyrite_web-0.1.0.tar.gz
| Download URL | pyrite_web-0.1.0.tar.gz |
|---|---|
| Size | 23.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
68ac4b742508043e36fa8a42805698225d23036ebdfe473c649cc7a9a775f5ec
|
|
BLAKE2b-256 checksum How to use checksums |
869d9eee1822a630138aaa881616d9e63f203009eb559662feedf1a2c5d2b991
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.8
|
Release files / pyrite_web-0.1.0-py3-none-any.whl
| Download URL | pyrite_web-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d88bd2fd0c877aa06729ca27849534101dac1b1a7c09816f8872c4820f876003
|
|
BLAKE2b-256 checksum How to use checksums |
3a23337dc2269bfc237b765363b8972d1911eb72b107fe545be5088bd20ceb20
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.8
|