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.
Files changed (123) hide show
  1. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/LICENSE +1 -1
  2. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/PKG-INFO +49 -17
  3. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/README.md +47 -15
  4. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyproject.toml +1 -2
  5. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/__init__.py +14 -3
  6. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/ai/guide.md +64 -10
  7. pyweb_stack-0.4.0/pyweb/app.py +78 -0
  8. pyweb_stack-0.4.0/pyweb/app_loader.py +279 -0
  9. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/asgi.py +25 -0
  10. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/auth.py +25 -3
  11. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/bench.py +31 -7
  12. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/browser.py +25 -0
  13. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/build.py +61 -55
  14. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/cli/__init__.py +56 -15
  15. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/ast.py +14 -0
  16. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/lower.py +383 -51
  17. pyweb_stack-0.4.0/pyweb/compiler/pipeline.py +302 -0
  18. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/pyjs.py +48 -3
  19. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/rpc.py +2 -1
  20. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/context.py +25 -0
  21. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/db/__init__.py +38 -1
  22. pyweb_stack-0.2.0/pyweb/npm.py → pyweb_stack-0.4.0/pyweb/dts.py +8 -9
  23. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/hosting.py +44 -6
  24. pyweb_stack-0.4.0/pyweb/livedata.py +338 -0
  25. pyweb_stack-0.4.0/pyweb/lsp.py +447 -0
  26. pyweb_stack-0.4.0/pyweb/markdown.py +254 -0
  27. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/mcp.py +198 -14
  28. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/models.py +2 -2
  29. pyweb_stack-0.4.0/pyweb/packages.py +748 -0
  30. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/realtime.py +192 -0
  31. pyweb_stack-0.4.0/pyweb/runtime/browser/markdown.js +195 -0
  32. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/runtime/browser/runtime.js +631 -54
  33. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/runtime/server/__init__.py +197 -34
  34. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/serve.py +23 -5
  35. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/ssr.py +134 -17
  36. pyweb_stack-0.4.0/pyweb/templates/ai-chat.pyweb +180 -0
  37. pyweb_stack-0.4.0/pyweb/templates/app.css +50 -0
  38. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/templates/chat.pyweb +9 -11
  39. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/testing.py +19 -9
  40. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb_stack.egg-info/PKG-INFO +49 -17
  41. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb_stack.egg-info/SOURCES.txt +13 -3
  42. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_auth.py +11 -0
  43. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_codegen.py +16 -0
  44. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_dts.py +5 -8
  45. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_forms_security_obs.py +3 -4
  46. pyweb_stack-0.4.0/tests/test_livedata.py +204 -0
  47. pyweb_stack-0.4.0/tests/test_lsp.py +204 -0
  48. pyweb_stack-0.4.0/tests/test_markdown.py +81 -0
  49. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_mcp.py +24 -2
  50. pyweb_stack-0.4.0/tests/test_multifile.py +182 -0
  51. pyweb_stack-0.4.0/tests/test_multipage.py +326 -0
  52. pyweb_stack-0.4.0/tests/test_packages.py +304 -0
  53. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_plugins_platform_bench.py +9 -1
  54. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_production_gaps.py +0 -15
  55. pyweb_stack-0.4.0/tests/test_realtime_transport.py +181 -0
  56. pyweb_stack-0.4.0/tests/test_streaming.py +276 -0
  57. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_sync_live.py +1 -1
  58. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_website.py +21 -1
  59. pyweb_stack-0.2.0/pyweb/app.py +0 -37
  60. pyweb_stack-0.2.0/pyweb/app_loader.py +0 -139
  61. pyweb_stack-0.2.0/pyweb/compiler/pipeline.py +0 -158
  62. pyweb_stack-0.2.0/pyweb/lsp.py +0 -214
  63. pyweb_stack-0.2.0/pyweb/templates/app.css +0 -28
  64. pyweb_stack-0.2.0/tests/test_lsp.py +0 -81
  65. pyweb_stack-0.2.0/tests/test_lsp_state.py +0 -76
  66. pyweb_stack-0.2.0/tests/test_realtime_transport.py +0 -77
  67. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/cache.py +0 -0
  68. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/cli/__main__.py +0 -0
  69. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/__init__.py +0 -0
  70. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/codegen/__init__.py +0 -0
  71. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/codegen/ir.py +0 -0
  72. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/errors.py +0 -0
  73. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/compiler/parser.py +0 -0
  74. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/css.py +0 -0
  75. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/db/migrate.py +0 -0
  76. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/decorators.py +0 -0
  77. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/deploy.py +0 -0
  78. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/forms.py +0 -0
  79. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/jobs.py +0 -0
  80. /pyweb_stack-0.2.0/pyweb/live.py → /pyweb_stack-0.4.0/pyweb/livetable.py +0 -0
  81. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/observability.py +0 -0
  82. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/platform.py +0 -0
  83. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/plugins.py +0 -0
  84. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/py.typed +0 -0
  85. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/rpc.py +0 -0
  86. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/security.py +0 -0
  87. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/sync.py +0 -0
  88. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/templates/auth.pyweb +0 -0
  89. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/templates/blog.pyweb +0 -0
  90. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/templates/counter.pyweb +0 -0
  91. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/templates/todo.pyweb +0 -0
  92. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb/uploads.py +0 -0
  93. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb_stack.egg-info/dependency_links.txt +0 -0
  94. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb_stack.egg-info/entry_points.txt +0 -0
  95. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb_stack.egg-info/requires.txt +0 -0
  96. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/pyweb_stack.egg-info/top_level.txt +0 -0
  97. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/setup.cfg +0 -0
  98. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_auth_contract.py +0 -0
  99. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_auth_security.py +0 -0
  100. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_auth_v1.py +0 -0
  101. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_backplane.py +0 -0
  102. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_browser_build.py +0 -0
  103. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_budget.py +0 -0
  104. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_cli_deploy.py +0 -0
  105. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_compiler.py +0 -0
  106. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_db_production.py +0 -0
  107. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_deploy_upload_render.py +0 -0
  108. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_docs.py +0 -0
  109. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_e2e.py +0 -0
  110. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_examples.py +0 -0
  111. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_jobs_cache_realtime.py +0 -0
  112. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_models_db.py +0 -0
  113. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_observability.py +0 -0
  114. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_parser.py +0 -0
  115. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_parser_v1.py +0 -0
  116. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_pyjs_semantics.py +0 -0
  117. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_reactivity.py +0 -0
  118. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_reference_apps.py +0 -0
  119. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_rpc_placement.py +0 -0
  120. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_rpc_production.py +0 -0
  121. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_runtime_signals.py +0 -0
  122. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_server_db.py +0 -0
  123. {pyweb_stack-0.2.0 → pyweb_stack-0.4.0}/tests/test_static_hashing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 MaanavKrishna and Claude
3
+ Copyright (c) 2026 MaanavKrishna
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyweb-stack
3
- Version: 0.2.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>, Claude <noreply@anthropic.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
  ![PyPI](https://img.shields.io/pypi/v/pyweb-stack)
65
65
  ![License](https://img.shields.io/badge/license-MIT-lightgrey)
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 ~10 KB
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 that need the npm
138
- ecosystem (use React/Svelte/Vue), or running scientific Python in the
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 numbers and
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, polling with `on_mount` |
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.1 is the first public release (beta). The language, server API,
206
- RPC protocol and CLI are documented and tested, and changes to them are
207
- announced in the changelog (see [stability](docs/15-toolkit.md#stability)). The test suite covers the
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) and Claude.
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
  ![PyPI](https://img.shields.io/pypi/v/pyweb-stack)
11
11
  ![License](https://img.shields.io/badge/license-MIT-lightgrey)
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 ~10 KB
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 that need the npm
84
- ecosystem (use React/Svelte/Vue), or running scientific Python in the
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 numbers and
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, polling with `on_mount` |
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.1 is the first public release (beta). The language, server API,
152
- RPC protocol and CLI are documented and tested, and changes to them are
153
- announced in the changelog (see [stability](docs/15-toolkit.md#stability)). The test suite covers the
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) and Claude.
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.2.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, live
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
- "live",
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}`). Components must be in the same file and their
139
- initial state must be computable in the browser (pass server data as props).
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
- - In handlers, navigate with `window.location.href = "/path"`.
170
- - Run code after load with a handler named `on_mount` (e.g.
171
- `setInterval(refresh, 2000)` for polling).
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
- JS functions, server-only names. Move such code into `@server` functions.
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 the same file (capitalized). |
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`), or write tests with `pyweb.testing.TestClient`.
212
- 5. Ship: `pyweb build app.pyweb --out dist --production` then
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