Integrations
Integrations overview
Use Pyzed as the desktop frontend for FastAPI, Django, SQLAlchemy, SQLModel, httpx, Pydantic and aiosqlite, with demos that follow each library's official tutorial.
Pyzed is only the interface: everything else in your app is ordinary Python. That means the libraries you already use for web backends, databases and HTTP work unchanged in a desktop app. This section shows how to wire the most common ones. Each demo starts from the library’s own official tutorial (FastAPI’s SQL tutorial, Django’s polls app, SQLAlchemy’s Quick Start, SQLModel’s heroes) and adds a Pyzed window to it.
When a backend has to run alongside the window, Architectures explains the four ways to combine them, with a diagram for each: same process, client and server, embedded server and in-process API.
| Demo | Libraries | What it shows | Tutorial |
|---|---|---|---|
fastapi_heroes |
FastAPI, SQLModel, httpx | FastAPI’s SQL tutorial API and a desktop client for it, run three ways: separate server, embedded server and in-process. | FastAPI |
django_polls |
Django, httpx | Django’s polls tutorial, used from a window through the ORM in the same process or over HTTP while runserver runs. | Django |
sqlalchemy_quickstart |
SQLAlchemy 2.0, aiosqlite | The ORM Quick Start’s users and addresses, with a sync Session in threads or an AsyncSession on the loop. | SQLAlchemy |
sqlalchemy_contacts |
SQLAlchemy 2.0 | 20,000 rows in a virtual table, searched and sorted in SQL from a worker thread. | SQLAlchemy |
sqlmodel_heroes |
SQLModel | SQLModel’s tutorial: teams, heroes, where() filters, relationships and a validated form. | SQLModel |
sqlmodel_inventory |
SQLModel, Pydantic | One model validates the form and defines the table. | SQLModel |
httpx_pypi |
httpx, Pydantic | Concurrent requests streamed into the window and parsed into models. | httpx |
httpx_download |
httpx | Streaming downloads with progress bars and cancel. | httpx |
pydantic_signup |
Pydantic | A sign-up form validated by a model, saved and loaded as JSON. | Pydantic |
aiosqlite_journal |
aiosqlite | An async SQLite connection on Pyzed’s event loop. | Other libraries |
Running a demo
Section titled “Running a demo”Each demo is a small uv project. From its folder:
cd examples/integrations/sqlalchemy_contactsuv run main.pycd examples/integrations/sqlalchemy_contactspython3 -m venv .venvsource .venv/bin/activatepip install pyzed-ui sqlalchemypython main.pycd examples/integrations/sqlalchemy_contactspy -m venv .venv.venv\Scripts\Activate.ps1pip install pyzed-ui sqlalchemypython main.pycd examples/integrations/sqlalchemy_contactspython3 -m pip install pyzed-ui sqlalchemypython3 main.pycd examples/integrations/sqlalchemy_contactspy -m pip install pyzed-ui sqlalchemypy main.pyuv run creates the virtual environment and installs the dependencies listed in the demo’s
pyproject.toml the first time. Inside the repository the demos use your checkout of Pyzed
(the [tool.uv.sources] table); to start your own app from one, copy the folder and delete
those two lines so pyzed-ui comes from PyPI.
Where your code runs
Section titled “Where your code runs”A Pyzed app has three places where code runs. Knowing which one a library call lands in is all it takes to integrate anything:
flowchart LR
UI["UI thread<br/>view() · update() · callbacks"]
Loop["asyncio loop (one thread)<br/>coroutines · async generators"]
Threads["worker threads<br/>Command.perform(function)"]
UI -- "return a coroutine" --> Loop
UI -- "Command.perform(fn, then)" --> Threads
Loop -- "result → update()" --> UI
Threads -- "then(result)" --> UI
| The library is… | Call it with | Examples |
|---|---|---|
async (await ...) |
Return a coroutine or an async generator from update, a callback or on_start. Its result goes to update. |
httpx AsyncClient, aiosqlite, Django’s async ORM, SQLAlchemy AsyncSession, websockets, redis.asyncio |
| blocking | Command.perform(function, then): function runs in a thread and then(result) on the UI thread. |
SQLAlchemy and SQLModel sessions, requests, pandas, file I/O |
| a server | Run it apart (fastapi dev, manage.py runserver) or in a daemon thread before pz.run. See Architectures. |
FastAPI, Django, Starlette |
| a stream | pz.listen(async_generator) in subscriptions(). |
websockets, server-sent events, Redis pub/sub |
Three rules keep it safe:
- Change
selfon the UI thread. Coroutines and threads return results;updateorthenstore them. Readingselffrom a coroutine is fine. - Never block the UI thread. A query that takes 50 ms freezes the window for 50 ms. Use
Command.performor an async library. - Ignore stale answers. When the user types a search, results can come back out of order.
Send the query along with the request and drop answers for an older one (every demo does
this in its
loadedhandler).
All coroutines share one asyncio loop (pz.asyncio_loop()), so objects tied to a loop, such
as an httpx.AsyncClient or an aiosqlite connection, can be created once and reused.
Validation errors on each field
Section titled “Validation errors on each field”Most backends report invalid input as a list of field errors. pz.validation_errors(error)
turns any of them into {field: message}, and ui.field(..., error=) shows the message under the
field title, in the theme’s error color:
from pydantic import ValidationError
def save(self): try: item = ItemIn.model_validate(self.draft) except ValidationError as error: self.errors = pz.validation_errors(error) return ...
def view(self): return ui.field("Price", ui.input(self.draft["price"], self.set_price), error=self.errors.get("price"))It understands:
- Pydantic and SQLModel
ValidationError(customValueErrormessages lose theirValue error,prefix), - the JSON of a FastAPI 422 response,
{"detail": [...]}, without thebody/querypart of each location, and a plain{"detail": "Not found"}, - Django’s
ValidationErrorfromfull_clean()(__all__errors use the key"").
Nested fields are joined with dots ("address.city"), and errors that belong to no field use
the key "", which the demos show in a ui.callout.
Selects with objects
Section titled “Selects with objects”ui.select accepts any objects as options and shows them with str(): a Django model, an enum,
a dataclass. on_select receives the object back, not its label:
ui.select(self.authors, self.draft["author"], lambda author: self.draft.update(author=author))