pyweb-stack 0.2.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/LICENSE +1 -1
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/PKG-INFO +49 -17
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/README.md +47 -15
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyproject.toml +1 -2
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/__init__.py +14 -3
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/ai/guide.md +64 -10
- pyweb_stack-0.4.0/pyweb/app.py +78 -0
- pyweb_stack-0.4.0/pyweb/app_loader.py +279 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/asgi.py +25 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/auth.py +25 -3
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/bench.py +31 -7
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/browser.py +25 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/build.py +61 -55
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/cli/__init__.py +56 -15
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/ast.py +14 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/lower.py +383 -51
- pyweb_stack-0.4.0/pyweb/compiler/pipeline.py +302 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/pyjs.py +48 -3
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/rpc.py +2 -1
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/context.py +25 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/db/__init__.py +38 -1
- pyweb_stack-0.2.0/pyweb/npm.py → pyweb_stack-0.4.0/pyweb/dts.py +8 -9
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/hosting.py +44 -6
- pyweb_stack-0.4.0/pyweb/livedata.py +338 -0
- pyweb_stack-0.4.0/pyweb/lsp.py +447 -0
- pyweb_stack-0.4.0/pyweb/markdown.py +254 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/mcp.py +198 -14
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/models.py +2 -2
- pyweb_stack-0.4.0/pyweb/packages.py +748 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/realtime.py +192 -0
- pyweb_stack-0.4.0/pyweb/runtime/browser/markdown.js +195 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/runtime/browser/runtime.js +631 -54
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/runtime/server/__init__.py +197 -34
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/serve.py +23 -5
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/ssr.py +134 -17
- pyweb_stack-0.4.0/pyweb/templates/ai-chat.pyweb +180 -0
- pyweb_stack-0.4.0/pyweb/templates/app.css +50 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/templates/chat.pyweb +9 -11
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/testing.py +19 -9
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb_stack.egg-info/PKG-INFO +49 -17
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb_stack.egg-info/SOURCES.txt +13 -3
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_auth.py +11 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_codegen.py +16 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_dts.py +5 -8
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_forms_security_obs.py +3 -4
- pyweb_stack-0.4.0/tests/test_livedata.py +204 -0
- pyweb_stack-0.4.0/tests/test_lsp.py +204 -0
- pyweb_stack-0.4.0/tests/test_markdown.py +81 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_mcp.py +24 -2
- pyweb_stack-0.4.0/tests/test_multifile.py +182 -0
- pyweb_stack-0.4.0/tests/test_multipage.py +326 -0
- pyweb_stack-0.4.0/tests/test_packages.py +304 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_plugins_platform_bench.py +9 -1
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_production_gaps.py +0 -15
- pyweb_stack-0.4.0/tests/test_realtime_transport.py +181 -0
- pyweb_stack-0.4.0/tests/test_streaming.py +276 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_sync_live.py +1 -1
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_website.py +21 -1
- pyweb_stack-0.2.0/pyweb/app.py +0 -37
- pyweb_stack-0.2.0/pyweb/app_loader.py +0 -139
- pyweb_stack-0.2.0/pyweb/compiler/pipeline.py +0 -158
- pyweb_stack-0.2.0/pyweb/lsp.py +0 -214
- pyweb_stack-0.2.0/pyweb/templates/app.css +0 -28
- pyweb_stack-0.2.0/tests/test_lsp.py +0 -81
- pyweb_stack-0.2.0/tests/test_lsp_state.py +0 -76
- pyweb_stack-0.2.0/tests/test_realtime_transport.py +0 -77
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/cache.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/cli/__main__.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/__init__.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/codegen/__init__.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/codegen/ir.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/errors.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/parser.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/css.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/db/migrate.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/decorators.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/deploy.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/forms.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/jobs.py +0 -0
- /pyweb_stack-0.2.0/pyweb/live.py → /pyweb_stack-0.4.0/pyweb/livetable.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/observability.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/platform.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/plugins.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/py.typed +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/rpc.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/security.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/sync.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/templates/auth.pyweb +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/templates/blog.pyweb +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/templates/counter.pyweb +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/templates/todo.pyweb +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/uploads.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb_stack.egg-info/dependency_links.txt +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb_stack.egg-info/entry_points.txt +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb_stack.egg-info/requires.txt +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb_stack.egg-info/top_level.txt +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/setup.cfg +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_auth_contract.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_auth_security.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_auth_v1.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_backplane.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_browser_build.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_budget.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_cli_deploy.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_compiler.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_db_production.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_deploy_upload_render.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_docs.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_e2e.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_examples.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_jobs_cache_realtime.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_models_db.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_observability.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_parser.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_parser_v1.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_pyjs_semantics.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_reactivity.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_reference_apps.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_rpc_placement.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_rpc_production.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_runtime_signals.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_server_db.py +0 -0
- {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_static_hashing.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyweb-stack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Full-stack web apps in one Python file: server-rendered pages, reactive browser UI, typed RPC.
|
|
5
|
-
Author-email: MaanavKrishna <67054795+MaanavKrishna@users.noreply.github.com
|
|
5
|
+
Author-email: MaanavKrishna <67054795+MaanavKrishna@users.noreply.github.com>
|
|
6
6
|
Maintainer-email: MaanavKrishna <67054795+MaanavKrishna@users.noreply.github.com>
|
|
7
7
|
License: MIT
|
|
8
8
|
Project-URL: Homepage, https://maanavkrishna.github.io/PyWeb/
|
|
@@ -64,6 +64,9 @@ functions, without a JavaScript toolchain.
|
|
|
64
64
|

|
|
65
65
|

|
|
66
66
|
|
|
67
|
+
**[Try it in your browser](https://maanavkrishna.github.io/PyWeb/playground.html)**: the playground runs
|
|
68
|
+
the real PyWeb, server functions included, on Python compiled to WebAssembly.
|
|
69
|
+
|
|
67
70
|
```pyweb
|
|
68
71
|
from pyweb import App, server
|
|
69
72
|
|
|
@@ -120,11 +123,20 @@ pyweb dev app.pyweb # http://localhost:8000
|
|
|
120
123
|
- **Boundaries are checked.** Database handles, imports and secrets can't
|
|
121
124
|
leak into browser code: it's a compile error with a line number.
|
|
122
125
|
`pyweb inspect` explains where every name runs and why.
|
|
126
|
+
- **Pages that keep up.** `live(db, "select ...")` keeps page data in
|
|
127
|
+
step with the database in every open window; `@server` functions that
|
|
128
|
+
`yield` stream to the browser (AI replies included, with a Stop
|
|
129
|
+
button that really stops).
|
|
130
|
+
- **Real multi-page apps.** Shared layouts that keep their state, links
|
|
131
|
+
that load without a full reload, typed query parameters, per-page
|
|
132
|
+
titles and social tags, error pages written in `.pyweb`.
|
|
133
|
+
- **npm without Node.** `pyweb add chart.js/auto` vendors a package for
|
|
134
|
+
browser code; no `node_modules`, no bundler.
|
|
123
135
|
- **Stateless servers.** Signed-cookie sessions and plain HTTP RPC scale
|
|
124
136
|
horizontally behind any load balancer. Deploy with `pyweb serve`,
|
|
125
137
|
uvicorn/gunicorn (ASGI) or the generated Dockerfile.
|
|
126
138
|
|
|
127
|
-
A typical interactive page ships under 1 KB of page code plus a ~
|
|
139
|
+
A typical interactive page ships under 1 KB of page code plus a ~14 KB
|
|
128
140
|
(gzip) runtime that's cached across pages. Pages without interactivity
|
|
129
141
|
ship no JavaScript.
|
|
130
142
|
|
|
@@ -134,17 +146,17 @@ ship no JavaScript.
|
|
|
134
146
|
small SaaS products and content sites with interactive parts, built by
|
|
135
147
|
people who'd rather stay in Python.
|
|
136
148
|
|
|
137
|
-
**Not a fit:** large client-heavy single-page apps
|
|
138
|
-
|
|
139
|
-
browser (use Pyodide/PyScript). See the
|
|
149
|
+
**Not a fit:** large client-heavy single-page apps built around a
|
|
150
|
+
JavaScript component framework (use React/Svelte/Vue), or running
|
|
151
|
+
scientific Python in the browser (use Pyodide/PyScript). See the
|
|
140
152
|
[comparison](https://maanavkrishna.github.io/PyWeb/introduction.html)
|
|
141
153
|
and [current limitations](docs/16-limitations-roadmap.md).
|
|
142
154
|
|
|
143
155
|
## Build it with AI
|
|
144
156
|
|
|
145
157
|
PyWeb ships an MCP server so AI assistants can scaffold, check, inspect,
|
|
146
|
-
render and test your app, with errors that come back as line
|
|
147
|
-
fix hints:
|
|
158
|
+
render, screenshot and test your app, with errors that come back as line
|
|
159
|
+
numbers and fix hints:
|
|
148
160
|
|
|
149
161
|
```bash
|
|
150
162
|
claude mcp add pyweb -- pyweb mcp # Claude Code
|
|
@@ -165,10 +177,10 @@ See [AI assistants & MCP](docs/17-ai-assistants.md).
|
|
|
165
177
|
| | |
|
|
166
178
|
|---|---|
|
|
167
179
|
| Start | [Introduction](docs/01-introduction.md) · [Quickstart](docs/02-quickstart.md) · [Tutorial](docs/03-tutorial.md) · [AI assistants & MCP](docs/17-ai-assistants.md) |
|
|
168
|
-
| Language | [`.pyweb` files](docs/04-pyweb-files.md) · [State & reactivity](docs/05-reactivity.md) · [Python in the browser](docs/07-browser-python.md) |
|
|
169
|
-
| Server | [Server functions & RPC](docs/06-server-functions.md) · [Pages & routing](docs/08-pages-routing-assets.md) · [Data](docs/09-data.md) · [Auth](docs/10-auth.md) |
|
|
180
|
+
| Language | [`.pyweb` files](docs/04-pyweb-files.md) · [State & reactivity](docs/05-reactivity.md) · [Python in the browser](docs/07-browser-python.md) · [npm packages](docs/18-npm-packages.md) |
|
|
181
|
+
| Server | [Server functions & RPC](docs/06-server-functions.md) · [Pages & routing](docs/08-pages-routing-assets.md) · [Layouts & navigation](docs/19-layouts-navigation.md) · [Data](docs/09-data.md) · [Live data](docs/21-live-data.md) · [Building AI apps](docs/20-ai-apps.md) · [Auth](docs/10-auth.md) |
|
|
170
182
|
| Ship | [Testing](docs/11-testing.md) · [Deployment](docs/12-deployment.md) · [Security](docs/13-security.md) · [CLI](docs/14-cli.md) |
|
|
171
|
-
| Reference | [Toolkit & stability](docs/15-toolkit.md) · [Limitations & roadmap](docs/16-limitations-roadmap.md) · [Architecture](ARCHITECTURE.md) · [Changelog](CHANGELOG.md) |
|
|
183
|
+
| Reference | [Recipe: wallets & web3](docs/22-recipe-web3.md) · [Toolkit & stability](docs/15-toolkit.md) · [Limitations & roadmap](docs/16-limitations-roadmap.md) · [Architecture](ARCHITECTURE.md) · [Changelog](CHANGELOG.md) |
|
|
172
184
|
|
|
173
185
|
The same docs are published at
|
|
174
186
|
**[maanavkrishna.github.io/PyWeb](https://maanavkrishna.github.io/PyWeb/)**,
|
|
@@ -185,26 +197,46 @@ a real browser by the test suite.
|
|
|
185
197
|
| [`todo`](examples/todo/app.pyweb) | components, list mutation, filters, keyed lists |
|
|
186
198
|
| [`blog`](examples/blog/app.pyweb) | SQL database, server functions, route params, 404s, validation errors |
|
|
187
199
|
| [`auth`](examples/auth/app.pyweb) | registration, password hashing, sessions, protected pages |
|
|
188
|
-
| [`chat`](examples/chat/app.pyweb) | route params, shared server state,
|
|
200
|
+
| [`chat`](examples/chat/app.pyweb) | route params, shared server state, live updates with `publish`/`subscribe` |
|
|
189
201
|
| [`showcase`](examples/showcase/app.pyweb) | everything on one page, with a stylesheet |
|
|
202
|
+
| [`site`](examples/site/app.pyweb) | a layout, client-side navigation, query parameters, page titles, a 404 page |
|
|
203
|
+
| [`dashboard`](examples/dashboard/app.pyweb) | live queries and a Chart.js chart from npm, in every open window |
|
|
204
|
+
| [`ai-chat`](examples/ai-chat/app.pyweb) | streaming AI replies with Stop and Markdown (Anthropic, OpenAI-compatible or a demo model) |
|
|
190
205
|
|
|
191
206
|
## Command line
|
|
192
207
|
|
|
193
208
|
```bash
|
|
194
|
-
pyweb new myapp --template todo # scaffold (blank|counter|todo|blog|auth|chat)
|
|
209
|
+
pyweb new myapp --template todo # scaffold (blank|counter|todo|blog|auth|chat|ai-chat)
|
|
210
|
+
pyweb add chart.js/auto # an npm package for browser code (no Node.js)
|
|
195
211
|
pyweb dev app.pyweb # dev server: live reload + error overlay
|
|
196
212
|
pyweb inspect app.pyweb # where each name runs, and why
|
|
197
213
|
pyweb check app.pyweb # compile + security checks for CI
|
|
198
214
|
pyweb build app.pyweb --out dist --production # self-contained, hashed, minified dist/
|
|
199
215
|
pyweb serve dist # production server (/healthz, CSP, graceful shutdown)
|
|
200
216
|
pyweb mcp # MCP server for AI assistants (stdio)
|
|
217
|
+
pyweb lsp # language server for editors (stdio)
|
|
201
218
|
```
|
|
202
219
|
|
|
220
|
+
Editors: the [VS Code extension](editors/vscode) adds highlighting, errors as you
|
|
221
|
+
type, hover that shows where code runs, completion and go to definition. Any
|
|
222
|
+
other LSP editor can run `pyweb lsp` ([setup](docs/14-cli.md#editor-support)).
|
|
223
|
+
|
|
203
224
|
## Status
|
|
204
225
|
|
|
205
|
-
PyWeb 0.
|
|
206
|
-
RPC protocol and CLI are documented and tested,
|
|
207
|
-
announced in the changelog
|
|
226
|
+
PyWeb is in beta (0.x; see the PyPI badge above for the latest version).
|
|
227
|
+
The language, server API, RPC protocol and CLI are documented and tested,
|
|
228
|
+
and changes to them are announced in the [changelog](CHANGELOG.md) (see
|
|
229
|
+
[stability](docs/15-toolkit.md#stability)). Upgrade with
|
|
230
|
+
`pip install -U pyweb-stack`.
|
|
231
|
+
|
|
232
|
+
| Version | Highlights |
|
|
233
|
+
|---|---|
|
|
234
|
+
| 0.4 | npm packages without Node, layouts and client-side navigation, streaming server functions and `<Markdown>` for AI apps, live queries, page head tags, `.pyweb` error pages |
|
|
235
|
+
| 0.3 | Hydration, live updates (SSE), multi-file apps, language server + VS Code extension, browser playground, faster rendering, screenshot/test MCP tools |
|
|
236
|
+
| 0.2 | MCP server for AI assistants, AI guide, project templates, `AGENTS.md`/`CLAUDE.md`, `llms.txt` |
|
|
237
|
+
| 0.1 | First public release: compiler, reactive runtime, server rendering, typed RPC, sessions, databases, CLI |
|
|
238
|
+
|
|
239
|
+
The test suite covers the
|
|
208
240
|
parser, the Python→JavaScript translation (differentially, against
|
|
209
241
|
CPython), the reactive runtime, server rendering, RPC, sessions, every
|
|
210
242
|
example app in Chromium, and the database/Redis layers against real
|
|
@@ -212,7 +244,7 @@ Postgres, MySQL and Redis servers, on Python 3.10–3.13.
|
|
|
212
244
|
|
|
213
245
|
## Authors
|
|
214
246
|
|
|
215
|
-
Built by [MaanavKrishna](https://github.com/MaanavKrishna)
|
|
247
|
+
Built by [MaanavKrishna](https://github.com/MaanavKrishna).
|
|
216
248
|
|
|
217
249
|
## Contributing
|
|
218
250
|
|
|
@@ -10,6 +10,9 @@ functions, without a JavaScript toolchain.
|
|
|
10
10
|

|
|
11
11
|

|
|
12
12
|
|
|
13
|
+
**[Try it in your browser](https://maanavkrishna.github.io/PyWeb/playground.html)**: the playground runs
|
|
14
|
+
the real PyWeb, server functions included, on Python compiled to WebAssembly.
|
|
15
|
+
|
|
13
16
|
```pyweb
|
|
14
17
|
from pyweb import App, server
|
|
15
18
|
|
|
@@ -66,11 +69,20 @@ pyweb dev app.pyweb # http://localhost:8000
|
|
|
66
69
|
- **Boundaries are checked.** Database handles, imports and secrets can't
|
|
67
70
|
leak into browser code: it's a compile error with a line number.
|
|
68
71
|
`pyweb inspect` explains where every name runs and why.
|
|
72
|
+
- **Pages that keep up.** `live(db, "select ...")` keeps page data in
|
|
73
|
+
step with the database in every open window; `@server` functions that
|
|
74
|
+
`yield` stream to the browser (AI replies included, with a Stop
|
|
75
|
+
button that really stops).
|
|
76
|
+
- **Real multi-page apps.** Shared layouts that keep their state, links
|
|
77
|
+
that load without a full reload, typed query parameters, per-page
|
|
78
|
+
titles and social tags, error pages written in `.pyweb`.
|
|
79
|
+
- **npm without Node.** `pyweb add chart.js/auto` vendors a package for
|
|
80
|
+
browser code; no `node_modules`, no bundler.
|
|
69
81
|
- **Stateless servers.** Signed-cookie sessions and plain HTTP RPC scale
|
|
70
82
|
horizontally behind any load balancer. Deploy with `pyweb serve`,
|
|
71
83
|
uvicorn/gunicorn (ASGI) or the generated Dockerfile.
|
|
72
84
|
|
|
73
|
-
A typical interactive page ships under 1 KB of page code plus a ~
|
|
85
|
+
A typical interactive page ships under 1 KB of page code plus a ~14 KB
|
|
74
86
|
(gzip) runtime that's cached across pages. Pages without interactivity
|
|
75
87
|
ship no JavaScript.
|
|
76
88
|
|
|
@@ -80,17 +92,17 @@ ship no JavaScript.
|
|
|
80
92
|
small SaaS products and content sites with interactive parts, built by
|
|
81
93
|
people who'd rather stay in Python.
|
|
82
94
|
|
|
83
|
-
**Not a fit:** large client-heavy single-page apps
|
|
84
|
-
|
|
85
|
-
browser (use Pyodide/PyScript). See the
|
|
95
|
+
**Not a fit:** large client-heavy single-page apps built around a
|
|
96
|
+
JavaScript component framework (use React/Svelte/Vue), or running
|
|
97
|
+
scientific Python in the browser (use Pyodide/PyScript). See the
|
|
86
98
|
[comparison](https://maanavkrishna.github.io/PyWeb/introduction.html)
|
|
87
99
|
and [current limitations](docs/16-limitations-roadmap.md).
|
|
88
100
|
|
|
89
101
|
## Build it with AI
|
|
90
102
|
|
|
91
103
|
PyWeb ships an MCP server so AI assistants can scaffold, check, inspect,
|
|
92
|
-
render and test your app, with errors that come back as line
|
|
93
|
-
fix hints:
|
|
104
|
+
render, screenshot and test your app, with errors that come back as line
|
|
105
|
+
numbers and fix hints:
|
|
94
106
|
|
|
95
107
|
```bash
|
|
96
108
|
claude mcp add pyweb -- pyweb mcp # Claude Code
|
|
@@ -111,10 +123,10 @@ See [AI assistants & MCP](docs/17-ai-assistants.md).
|
|
|
111
123
|
| | |
|
|
112
124
|
|---|---|
|
|
113
125
|
| Start | [Introduction](docs/01-introduction.md) · [Quickstart](docs/02-quickstart.md) · [Tutorial](docs/03-tutorial.md) · [AI assistants & MCP](docs/17-ai-assistants.md) |
|
|
114
|
-
| Language | [`.pyweb` files](docs/04-pyweb-files.md) · [State & reactivity](docs/05-reactivity.md) · [Python in the browser](docs/07-browser-python.md) |
|
|
115
|
-
| Server | [Server functions & RPC](docs/06-server-functions.md) · [Pages & routing](docs/08-pages-routing-assets.md) · [Data](docs/09-data.md) · [Auth](docs/10-auth.md) |
|
|
126
|
+
| Language | [`.pyweb` files](docs/04-pyweb-files.md) · [State & reactivity](docs/05-reactivity.md) · [Python in the browser](docs/07-browser-python.md) · [npm packages](docs/18-npm-packages.md) |
|
|
127
|
+
| Server | [Server functions & RPC](docs/06-server-functions.md) · [Pages & routing](docs/08-pages-routing-assets.md) · [Layouts & navigation](docs/19-layouts-navigation.md) · [Data](docs/09-data.md) · [Live data](docs/21-live-data.md) · [Building AI apps](docs/20-ai-apps.md) · [Auth](docs/10-auth.md) |
|
|
116
128
|
| Ship | [Testing](docs/11-testing.md) · [Deployment](docs/12-deployment.md) · [Security](docs/13-security.md) · [CLI](docs/14-cli.md) |
|
|
117
|
-
| Reference | [Toolkit & stability](docs/15-toolkit.md) · [Limitations & roadmap](docs/16-limitations-roadmap.md) · [Architecture](ARCHITECTURE.md) · [Changelog](CHANGELOG.md) |
|
|
129
|
+
| Reference | [Recipe: wallets & web3](docs/22-recipe-web3.md) · [Toolkit & stability](docs/15-toolkit.md) · [Limitations & roadmap](docs/16-limitations-roadmap.md) · [Architecture](ARCHITECTURE.md) · [Changelog](CHANGELOG.md) |
|
|
118
130
|
|
|
119
131
|
The same docs are published at
|
|
120
132
|
**[maanavkrishna.github.io/PyWeb](https://maanavkrishna.github.io/PyWeb/)**,
|
|
@@ -131,26 +143,46 @@ a real browser by the test suite.
|
|
|
131
143
|
| [`todo`](examples/todo/app.pyweb) | components, list mutation, filters, keyed lists |
|
|
132
144
|
| [`blog`](examples/blog/app.pyweb) | SQL database, server functions, route params, 404s, validation errors |
|
|
133
145
|
| [`auth`](examples/auth/app.pyweb) | registration, password hashing, sessions, protected pages |
|
|
134
|
-
| [`chat`](examples/chat/app.pyweb) | route params, shared server state,
|
|
146
|
+
| [`chat`](examples/chat/app.pyweb) | route params, shared server state, live updates with `publish`/`subscribe` |
|
|
135
147
|
| [`showcase`](examples/showcase/app.pyweb) | everything on one page, with a stylesheet |
|
|
148
|
+
| [`site`](examples/site/app.pyweb) | a layout, client-side navigation, query parameters, page titles, a 404 page |
|
|
149
|
+
| [`dashboard`](examples/dashboard/app.pyweb) | live queries and a Chart.js chart from npm, in every open window |
|
|
150
|
+
| [`ai-chat`](examples/ai-chat/app.pyweb) | streaming AI replies with Stop and Markdown (Anthropic, OpenAI-compatible or a demo model) |
|
|
136
151
|
|
|
137
152
|
## Command line
|
|
138
153
|
|
|
139
154
|
```bash
|
|
140
|
-
pyweb new myapp --template todo # scaffold (blank|counter|todo|blog|auth|chat)
|
|
155
|
+
pyweb new myapp --template todo # scaffold (blank|counter|todo|blog|auth|chat|ai-chat)
|
|
156
|
+
pyweb add chart.js/auto # an npm package for browser code (no Node.js)
|
|
141
157
|
pyweb dev app.pyweb # dev server: live reload + error overlay
|
|
142
158
|
pyweb inspect app.pyweb # where each name runs, and why
|
|
143
159
|
pyweb check app.pyweb # compile + security checks for CI
|
|
144
160
|
pyweb build app.pyweb --out dist --production # self-contained, hashed, minified dist/
|
|
145
161
|
pyweb serve dist # production server (/healthz, CSP, graceful shutdown)
|
|
146
162
|
pyweb mcp # MCP server for AI assistants (stdio)
|
|
163
|
+
pyweb lsp # language server for editors (stdio)
|
|
147
164
|
```
|
|
148
165
|
|
|
166
|
+
Editors: the [VS Code extension](editors/vscode) adds highlighting, errors as you
|
|
167
|
+
type, hover that shows where code runs, completion and go to definition. Any
|
|
168
|
+
other LSP editor can run `pyweb lsp` ([setup](docs/14-cli.md#editor-support)).
|
|
169
|
+
|
|
149
170
|
## Status
|
|
150
171
|
|
|
151
|
-
PyWeb 0.
|
|
152
|
-
RPC protocol and CLI are documented and tested,
|
|
153
|
-
announced in the changelog
|
|
172
|
+
PyWeb is in beta (0.x; see the PyPI badge above for the latest version).
|
|
173
|
+
The language, server API, RPC protocol and CLI are documented and tested,
|
|
174
|
+
and changes to them are announced in the [changelog](CHANGELOG.md) (see
|
|
175
|
+
[stability](docs/15-toolkit.md#stability)). Upgrade with
|
|
176
|
+
`pip install -U pyweb-stack`.
|
|
177
|
+
|
|
178
|
+
| Version | Highlights |
|
|
179
|
+
|---|---|
|
|
180
|
+
| 0.4 | npm packages without Node, layouts and client-side navigation, streaming server functions and `<Markdown>` for AI apps, live queries, page head tags, `.pyweb` error pages |
|
|
181
|
+
| 0.3 | Hydration, live updates (SSE), multi-file apps, language server + VS Code extension, browser playground, faster rendering, screenshot/test MCP tools |
|
|
182
|
+
| 0.2 | MCP server for AI assistants, AI guide, project templates, `AGENTS.md`/`CLAUDE.md`, `llms.txt` |
|
|
183
|
+
| 0.1 | First public release: compiler, reactive runtime, server rendering, typed RPC, sessions, databases, CLI |
|
|
184
|
+
|
|
185
|
+
The test suite covers the
|
|
154
186
|
parser, the Python→JavaScript translation (differentially, against
|
|
155
187
|
CPython), the reactive runtime, server rendering, RPC, sessions, every
|
|
156
188
|
example app in Chromium, and the database/Redis layers against real
|
|
@@ -158,7 +190,7 @@ Postgres, MySQL and Redis servers, on Python 3.10–3.13.
|
|
|
158
190
|
|
|
159
191
|
## Authors
|
|
160
192
|
|
|
161
|
-
Built by [MaanavKrishna](https://github.com/MaanavKrishna)
|
|
193
|
+
Built by [MaanavKrishna](https://github.com/MaanavKrishna).
|
|
162
194
|
|
|
163
195
|
## Contributing
|
|
164
196
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyweb-stack"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Full-stack web apps in one Python file: server-rendered pages, reactive browser UI, typed RPC."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -12,7 +12,6 @@ requires-python = ">=3.10"
|
|
|
12
12
|
dependencies = []
|
|
13
13
|
authors = [
|
|
14
14
|
{ name = "MaanavKrishna", email = "67054795+MaanavKrishna@users.noreply.github.com" },
|
|
15
|
-
{ name = "Claude", email = "noreply@anthropic.com" },
|
|
16
15
|
]
|
|
17
16
|
maintainers = [{ name = "MaanavKrishna", email = "67054795+MaanavKrishna@users.noreply.github.com" }]
|
|
18
17
|
keywords = ["web", "framework", "full-stack", "reactive", "rpc", "ssr", "compiler"]
|
|
@@ -3,25 +3,36 @@
|
|
|
3
3
|
from .app import App
|
|
4
4
|
from .models import Email, Model
|
|
5
5
|
from . import auth, cache, jobs, realtime, security, observability, forms, testing
|
|
6
|
-
from . import sync, deploy, uploads, lsp,
|
|
6
|
+
from . import sync, deploy, uploads, lsp, livetable
|
|
7
7
|
from . import browser as browser_api
|
|
8
8
|
from . import build, css, plugins, platform
|
|
9
9
|
from .jobs import task
|
|
10
|
-
from .context import NotFound, redirect, request, session
|
|
10
|
+
from .context import NotFound, head, redirect, request, session
|
|
11
11
|
from .rpc import RPCError
|
|
12
12
|
from .decorators import component, edge, server, worker
|
|
13
|
+
from .realtime import channel, publish, subscribe
|
|
14
|
+
from .packages import npm
|
|
15
|
+
from .markdown import Markdown
|
|
16
|
+
from .livedata import live
|
|
13
17
|
|
|
14
18
|
__all__ = [
|
|
15
19
|
"App",
|
|
16
20
|
"request",
|
|
17
21
|
"session",
|
|
18
22
|
"redirect",
|
|
23
|
+
"head",
|
|
19
24
|
"NotFound",
|
|
20
25
|
"RPCError",
|
|
21
26
|
"component",
|
|
22
27
|
"edge",
|
|
23
28
|
"server",
|
|
24
29
|
"worker",
|
|
30
|
+
"channel",
|
|
31
|
+
"publish",
|
|
32
|
+
"subscribe",
|
|
33
|
+
"npm",
|
|
34
|
+
"Markdown",
|
|
35
|
+
"live",
|
|
25
36
|
"Email",
|
|
26
37
|
"Model",
|
|
27
38
|
"task",
|
|
@@ -34,7 +45,7 @@ __all__ = [
|
|
|
34
45
|
"forms",
|
|
35
46
|
"testing",
|
|
36
47
|
"sync",
|
|
37
|
-
"
|
|
48
|
+
"livetable",
|
|
38
49
|
"deploy",
|
|
39
50
|
"uploads",
|
|
40
51
|
"lsp",
|
|
@@ -135,8 +135,13 @@ def Home():
|
|
|
135
135
|
|
|
136
136
|
Props are parameters (defaults = optional). Pass callbacks as props
|
|
137
137
|
(`on_delete={lambda: delete(i)}`) and use them as handlers inside
|
|
138
|
-
(`onclick={on_delete}`).
|
|
139
|
-
|
|
138
|
+
(`onclick={on_delete}`). A component's initial state must be computable in
|
|
139
|
+
the browser (pass server data as props).
|
|
140
|
+
|
|
141
|
+
Bigger apps can split into files: put components, `@server` functions and
|
|
142
|
+
constants in e.g. `widgets.pyweb` (or `ui/cards.pyweb`) next to `app.pyweb`
|
|
143
|
+
and `from widgets import Card, save`. Pages stay in `app.pyweb`; import
|
|
144
|
+
server functions without `as`; each file keeps its own constants.
|
|
140
145
|
|
|
141
146
|
## 6. Server functions, sessions, routing
|
|
142
147
|
|
|
@@ -166,9 +171,41 @@ def Item(item_id: int):
|
|
|
166
171
|
- Database: `from pyweb.db import connect; db = connect("sqlite:///app.db")`;
|
|
167
172
|
`db.execute("select ... where id = ?", (x,)).dicts()`; always use `?`
|
|
168
173
|
parameters; `with db.transaction(): ...`.
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
- Query parameters: page params not in the route come from the query
|
|
175
|
+
string, typed by annotation: `def Search(q: str = "", page: int = 1)`.
|
|
176
|
+
Missing required / bad values -> 400.
|
|
177
|
+
- Layouts: `@app.layout` (or `@app.layout("/admin")`) on a function with
|
|
178
|
+
markup containing `{children}` once wraps every page under the prefix;
|
|
179
|
+
`@app.page(..., layout=None)` opts out. Links between pages load without
|
|
180
|
+
a full reload and keep the layout's state; mark nothing yourself: links
|
|
181
|
+
to the current page get `aria-current="page"` automatically.
|
|
182
|
+
- In handlers, navigate with `navigate("/path")` (`from pyweb.browser import navigate`).
|
|
183
|
+
- Head tags: `@app.page("/", title=..., description=..., image=...)`, or
|
|
184
|
+
`head(title=..., description=...)` in the page body (server). `App(base_url=...)`
|
|
185
|
+
adds canonical URLs.
|
|
186
|
+
- Error pages: `@app.error(404)` on a page function taking `path` (and/or
|
|
187
|
+
`status`, `message`, `request_id`).
|
|
188
|
+
- Run code after load with a handler named `on_mount`; stop timers in
|
|
189
|
+
`on_unmount` (runs when the user navigates away). React to a value
|
|
190
|
+
changing with `watch(lambda: value, handler)` in `on_mount`
|
|
191
|
+
(`from pyweb.browser import watch`).
|
|
192
|
+
- Live data: `rows = live(db, "select ... where x = ?", (x,))` in a page
|
|
193
|
+
(`from pyweb import live`) renders the rows and keeps them current in
|
|
194
|
+
every open page when the tables are written through `pyweb.db`. No
|
|
195
|
+
publish/subscribe needed. Use `db.notify("table")` after writes made
|
|
196
|
+
outside `pyweb.db`. Keep live queries small (`LIMIT`).
|
|
197
|
+
- Streaming (AI replies, progress): a `@server` function that `yield`s;
|
|
198
|
+
in an `async def` handler `stream = fn(...)` then
|
|
199
|
+
`async for piece in stream: ...`; `stream.cancel()` stops it (closes the
|
|
200
|
+
generator on the server). Show model output with `<Markdown text={reply} />`
|
|
201
|
+
(`from pyweb import Markdown`): safe, renders as it streams. Template:
|
|
202
|
+
`pyweb new NAME --template ai-chat`. Keep API keys in server code
|
|
203
|
+
(`os.environ`), never in page variables.
|
|
204
|
+
- Live updates: in a server function `publish("room:1", data)`; in the
|
|
205
|
+
page `feed = channel("room:1")` (runs on the server); in `on_mount`
|
|
206
|
+
`subscribe(feed, handler)`, where `handler(message)` assigns page
|
|
207
|
+
variables. Import all three from `pyweb`. Never poll with
|
|
208
|
+
`setInterval` when `publish` fits.
|
|
172
209
|
|
|
173
210
|
## 7. Python that compiles to the browser
|
|
174
211
|
|
|
@@ -183,7 +220,16 @@ JS globals are available directly: `window`, `document`, `localStorage`,
|
|
|
183
220
|
|
|
184
221
|
Not supported in browser code: classes, imports, `with`, generators,
|
|
185
222
|
walrus, `*args/**kwargs` parameters, slice assignment, keyword arguments to
|
|
186
|
-
|
|
223
|
+
browser globals, server-only names. Move such code into `@server` functions.
|
|
224
|
+
|
|
225
|
+
npm packages (no Node.js needed): run `pyweb add chart.js/auto` in the app
|
|
226
|
+
folder (writes `pyweb.lock` and `static/vendor/`; commit both), then bind at
|
|
227
|
+
module level with literal strings: `Chart = npm("chart.js/auto")`,
|
|
228
|
+
`Gauge = npm("pkg", "Gauge")` (named export), `lib = npm("pkg", "*")` (whole
|
|
229
|
+
module). Calling a class constructs it (`new`); keyword arguments become one
|
|
230
|
+
options object. npm names work only in browser code (handlers, `on_mount`,
|
|
231
|
+
lambdas), never directly in markup. Give libraries an element with
|
|
232
|
+
`ref={el}` (declare `el = None`; it is set before `on_mount`).
|
|
187
233
|
|
|
188
234
|
## 8. Errors and fixes
|
|
189
235
|
|
|
@@ -195,21 +241,29 @@ JS functions, server-only names. Move such code into `@server` functions.
|
|
|
195
241
|
| `cannot assign to ... derived/read-only` | Assign to a variable the handler owns (make it state), not a computed/constant. |
|
|
196
242
|
| `server secret ... would be sent to the browser` | Keep the value inside `@server` functions; don't read it in markup/handlers. |
|
|
197
243
|
| `bind={x} must name a local variable` | Declare `x = ""` (or a number/bool) in the page before the markup. |
|
|
198
|
-
| `unknown component <X>` | Define `def X(...)` with markup in
|
|
244
|
+
| `unknown component <X>` | Define `def X(...)` with markup (capitalized) in this file, or import it: `from widgets import X`. |
|
|
199
245
|
| `mismatched </tag>` / `is never closed` | Close every tag; void tags (`input`, `img`, `br`) need no close (`<input ... />`). |
|
|
200
246
|
| `... is not supported in browser code` | Rewrite with supported constructs or move it to `@server`. |
|
|
247
|
+
| `npm package 'x' isn't installed` | Run `pyweb add x` in the app folder (the folder with `pyweb.lock`). |
|
|
248
|
+
| `uses X from npm(...), which only exists in the browser` | Use the package in a handler or `on_mount` and store the result in a page variable that markup shows. |
|
|
249
|
+
| `layout ... has no {children}` | Put `{children}` exactly once in the layout's markup where pages go. |
|
|
250
|
+
| `isn't a valid int` / `missing ?name=` (400) | Give the query parameter a default, or link with a valid value. |
|
|
251
|
+
| `pyweb add`: `is CommonJS` / `imports the Node.js module` | Pick an ES-module browser package (e.g. `lodash-es`), or do the work in an `@server` function. |
|
|
201
252
|
|
|
202
253
|
## 9. Workflow for agents
|
|
203
254
|
|
|
204
255
|
1. Start from a template: `pyweb new NAME --template todo` (or the
|
|
205
|
-
`pyweb_new_app` MCP tool). Templates: blank, counter, todo, blog, auth, chat.
|
|
256
|
+
`pyweb_new_app` MCP tool). Templates: blank, counter, todo, blog, auth, chat, ai-chat.
|
|
206
257
|
2. Edit `app.pyweb`. After every edit run `pyweb check app.pyweb`
|
|
207
258
|
(MCP: `pyweb_check`) and fix errors by line number.
|
|
208
259
|
3. Use `pyweb inspect` (MCP: `pyweb_inspect`) to confirm what runs in the
|
|
209
260
|
browser vs server and what is sent to the browser.
|
|
210
261
|
4. Verify behaviour: render pages (`pyweb_render`) and call server
|
|
211
|
-
functions (`pyweb_call`)
|
|
212
|
-
|
|
262
|
+
functions (`pyweb_call`). See the page and try interactions in a real
|
|
263
|
+
browser with `pyweb_screenshot` (steps: click, fill, press, ...).
|
|
264
|
+
5. Test: new apps include `test_app.py` (`pyweb.testing.TestClient`); add
|
|
265
|
+
tests for what you change and run `pytest` (MCP: `pyweb_test`).
|
|
266
|
+
6. Ship: `pyweb build app.pyweb --out dist --production` then
|
|
213
267
|
`pyweb serve dist` (set `PYWEB_AUTH_SECRET` in production).
|
|
214
268
|
|
|
215
269
|
Full docs: https://maanavkrishna.github.io/PyWeb/
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Application object: page registry and configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class App:
|
|
7
|
+
"""The app: its pages, layouts, error pages and site-wide settings.
|
|
8
|
+
|
|
9
|
+
``base_url`` (e.g. ``"https://example.com"``) gives every page a
|
|
10
|
+
canonical URL and absolute social-card links; ``description`` and
|
|
11
|
+
``image`` are the defaults for pages that don't set their own.
|
|
12
|
+
``client_nav=False`` makes links do full page loads.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, database=None, cache=None, auth=None, plugins=(), *,
|
|
16
|
+
title="PyWeb", stylesheets=(), lang="en", base_url=None, description=None,
|
|
17
|
+
image=None, client_nav=True):
|
|
18
|
+
self.title = title
|
|
19
|
+
self.stylesheets = list(stylesheets)
|
|
20
|
+
self.lang = lang
|
|
21
|
+
self.base_url = base_url.rstrip("/") if base_url else None
|
|
22
|
+
self.description = description
|
|
23
|
+
self.image = image
|
|
24
|
+
self.client_nav = client_nav
|
|
25
|
+
self.database = database
|
|
26
|
+
self.cache = cache
|
|
27
|
+
self.auth = auth
|
|
28
|
+
self.pages: list[tuple[str, str, dict]] = []
|
|
29
|
+
self.layouts: list[tuple[str, str]] = []
|
|
30
|
+
self.errors: dict[int, str] = {}
|
|
31
|
+
from pyweb.plugins import Registry
|
|
32
|
+
self.plugins = Registry()
|
|
33
|
+
for plugin in plugins:
|
|
34
|
+
self.use(plugin)
|
|
35
|
+
|
|
36
|
+
def page(self, route, *, title=None, render="server", description=None, image=None, canonical=None,
|
|
37
|
+
noindex=False, layout=...):
|
|
38
|
+
"""Register a page at ``route``. ``{name}`` segments and other parameters of the
|
|
39
|
+
function (from the query string) become its arguments."""
|
|
40
|
+
def deco(fn):
|
|
41
|
+
self.pages.append((route, fn.__name__, {"render": render, "title": title}))
|
|
42
|
+
fn.__pyweb_route__ = route
|
|
43
|
+
fn.__pyweb_title__ = title
|
|
44
|
+
fn.__pyweb_render__ = render
|
|
45
|
+
return fn
|
|
46
|
+
|
|
47
|
+
return deco
|
|
48
|
+
|
|
49
|
+
def layout(self, prefix="/"):
|
|
50
|
+
"""Wrap every page under ``prefix`` in this function's markup (``{children}`` is the page).
|
|
51
|
+
|
|
52
|
+
Use as ``@app.layout`` or ``@app.layout("/admin")``.
|
|
53
|
+
"""
|
|
54
|
+
if callable(prefix):
|
|
55
|
+
self.layouts.append(("/", prefix.__name__))
|
|
56
|
+
return prefix
|
|
57
|
+
|
|
58
|
+
def deco(fn):
|
|
59
|
+
self.layouts.append((prefix, fn.__name__))
|
|
60
|
+
return fn
|
|
61
|
+
|
|
62
|
+
return deco
|
|
63
|
+
|
|
64
|
+
def error(self, status, *, title=None, layout=...):
|
|
65
|
+
"""Render this page for HTTP ``status`` (404, 500, ...) instead of the built-in one."""
|
|
66
|
+
def deco(fn):
|
|
67
|
+
self.errors[status] = fn.__name__
|
|
68
|
+
return fn
|
|
69
|
+
|
|
70
|
+
return deco
|
|
71
|
+
|
|
72
|
+
def use(self, plugin):
|
|
73
|
+
"""Attach a :class:`pyweb.plugins.Plugin`; merges its routes."""
|
|
74
|
+
self.plugins.add(plugin)
|
|
75
|
+
for route, fn in plugin.routes:
|
|
76
|
+
self.pages.append((route, fn.__name__, {"render": "server",
|
|
77
|
+
"plugin": plugin.name}))
|
|
78
|
+
return plugin
|