Architectures
A Pyzed window is a Python program, so a backend library can sit next to it in four different ways. The choice depends on one question: who else needs the data?
| Architecture | Who uses the data | Network | Demos |
|---|---|---|---|
| 1. Same process | Only this desktop app | None | SQLAlchemy, SQLModel, Django ORM, aiosqlite, Pydantic |
| 2. Client and server | A website, a phone app, other users | HTTP to a server | FastAPI client.py, Django client.py, httpx |
| 3. Embedded server | This app and other programs on the same computer | Local port | FastAPI embedded.py |
| 4. In-process API | Only this app, but the code is an API | None | FastAPI in_process.py |
All four can share the same code: the models and the API don’t know whether a window, a browser or a test calls them. That is what lets an app start with architecture 1 and move to 2 later.
1. Same process
Section titled “1. Same process”The window imports the library and calls it directly. There is no server and nothing listens on the network. This is the default for a desktop app with its own database.
flowchart LR
subgraph app["Your program (uv run main.py)"]
W["Pyzed window<br/>view · update"]
T["Worker threads<br/>Command.perform"]
L["asyncio loop<br/>coroutines"]
M["Models and queries<br/>SQLAlchemy · SQLModel · Django ORM"]
W --> T --> M
W --> L --> M
end
M --> DB[("SQLite / PostgreSQL")]
- Blocking libraries (a SQLAlchemy
Session, SQLModel, the Django ORM) run in a worker thread withCommand.perform(function, then). - Async libraries (
AsyncSession, aiosqlite) run as coroutines on Pyzed’s loop. - The database can still be shared: the Django demo’s
desktop.pyandmanage.py runserveruse the samedb.sqlite3at the same time.
Demos: sqlalchemy_quickstart (both backends),
sqlmodel_heroes, django_polls desktop.py,
aiosqlite_journal, pydantic_signup.
2. Client and server
Section titled “2. Client and server”The backend is a separate program, started with its own tool (fastapi dev, manage.py runserver,
a deployment on a server). The window is one more client and only speaks HTTP, usually with httpx.
flowchart LR
subgraph server["Server process (fastapi dev main.py · manage.py runserver)"]
API["FastAPI / Django<br/>routes and views"] --> DB[("Database")]
end
subgraph desktop["Desktop process (uv run client.py)"]
W["Pyzed window"] --> H["httpx.AsyncClient"]
end
H -- "HTTP / JSON" --> API
B["Browser · phone app"] -- "HTTP" --> API
- Use it when the data must be shared between users or devices, or the API already exists.
- The window needs neither the database driver nor the backend framework installed.
- Errors arrive as HTTP statuses: a FastAPI 422 goes straight into
pz.validation_errors.
Demos: fastapi_heroes client.py, django_polls
client.py, httpx_pypi (a public API).
3. Embedded server
Section titled “3. Embedded server”One program starts the API in a background thread with Uvicorn and then opens the window. The API is available on a local port while the window is open, for a browser, a script or another app.
flowchart LR
subgraph app["Your program (uv run embedded.py)"]
direction LR
W["Pyzed window<br/>main thread"] -- "httpx" --> U
U["Uvicorn in a daemon thread<br/>FastAPI app"] --> DB[("Database")]
end
B["Browser at /docs<br/>other local programs"] -- "http://127.0.0.1:8000" --> U
- Use it for a tool that also exposes an API or a web page, such as a local dashboard or a device that receives webhooks.
- The server stops with the window, because its thread is a daemon.
- Bind to
127.0.0.1unless other computers must connect.
Demo: fastapi_heroes embedded.py.
4. In-process API
Section titled “4. In-process API”httpx can send requests straight to an ASGI app with httpx.ASGITransport, without a server or
a port. The code is written as an API and a client, but runs as one offline program.
flowchart LR
subgraph app["Your program (uv run in_process.py)"]
W["Pyzed window"] --> H["httpx.AsyncClient<br/>transport=ASGITransport(app)"]
H -- "function call, no network" --> API["FastAPI app"]
API --> DB[("Database")]
end
- Use it to ship the same API as a desktop app that works offline, or to test the client.
ASGITransportdoesn’t run startup or lifespan events: call your setup code yourself.
Demo: fastapi_heroes in_process.py.
Which one fits each library
Section titled “Which one fits each library”| Library | Same process | Client and server | Embedded server | In-process API |
|---|---|---|---|---|
| FastAPI | Import its models only | ✅ fastapi dev / fastapi run |
✅ Uvicorn in a thread | ✅ ASGITransport |
| Django | ✅ ORM after django.setup() |
✅ manage.py runserver |
Not recommended: run it apart | Possible through its ASGI app |
| SQLAlchemy | ✅ Session or AsyncSession |
Behind your API | — | — |
| SQLModel | ✅ Session |
Behind FastAPI | — | — |
| httpx | — | ✅ the client | ✅ the client | ✅ the transport |
| Pydantic | ✅ validates the form | ✅ parses the responses | ✅ | ✅ |
| aiosqlite | ✅ on Pyzed’s loop | — | — | — |
Structure of a project
Section titled “Structure of a project”Keep the backend free of Pyzed and the window free of SQL. The demos all follow this layout, which lets the same backend serve a window, an API and tests:
myapp/├── pyproject.toml├── models.py # SQLModel / SQLAlchemy / Django models, Pydantic schemas├── db.py # the queries, one function per action, no Pyzed imports├── main.py # FastAPI app or Django project (when there is a server)└── window.py # the Pyzed App: calls db.py or the API