pyweb-stack 0.4.4__tar.gz → 0.5.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 (192) hide show
  1. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/PKG-INFO +142 -35
  2. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/README.md +130 -34
  3. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyproject.toml +7 -4
  4. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/__init__.py +21 -1
  5. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/ai/guide.md +81 -17
  6. pyweb_stack-0.5.0/pyweb/app.py +136 -0
  7. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/app_loader.py +63 -0
  8. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/asgi.py +53 -1
  9. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/auth.py +20 -0
  10. pyweb_stack-0.5.0/pyweb/authkit/__init__.py +425 -0
  11. pyweb_stack-0.5.0/pyweb/authkit/admin.py +247 -0
  12. pyweb_stack-0.5.0/pyweb/authkit/html.py +107 -0
  13. pyweb_stack-0.5.0/pyweb/authkit/models.py +134 -0
  14. pyweb_stack-0.5.0/pyweb/authkit/oauth.py +107 -0
  15. pyweb_stack-0.5.0/pyweb/authkit/pages.py +765 -0
  16. pyweb_stack-0.5.0/pyweb/authkit/passwords.py +158 -0
  17. pyweb_stack-0.5.0/pyweb/authkit/throttle.py +88 -0
  18. pyweb_stack-0.5.0/pyweb/authkit/webauthn.py +210 -0
  19. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/build.py +20 -11
  20. pyweb_stack-0.5.0/pyweb/cli/__init__.py +861 -0
  21. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/lower.py +247 -29
  22. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/pipeline.py +1 -0
  23. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/config.py +33 -3
  24. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/context.py +1 -1
  25. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/db/__init__.py +365 -37
  26. pyweb_stack-0.5.0/pyweb/db/dialect.py +305 -0
  27. pyweb_stack-0.5.0/pyweb/db/migrate.py +567 -0
  28. pyweb_stack-0.5.0/pyweb/db/schema.py +884 -0
  29. pyweb_stack-0.5.0/pyweb/db/seeds.py +56 -0
  30. pyweb_stack-0.5.0/pyweb/decorators.py +108 -0
  31. pyweb_stack-0.5.0/pyweb/deploy/__init__.py +16 -0
  32. pyweb_stack-0.5.0/pyweb/deploy/plan.py +285 -0
  33. pyweb_stack-0.5.0/pyweb/deploy/targets.py +498 -0
  34. pyweb_stack-0.5.0/pyweb/deprecation.py +34 -0
  35. pyweb_stack-0.5.0/pyweb/forms.py +828 -0
  36. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/hosting.py +58 -6
  37. pyweb_stack-0.5.0/pyweb/jobs/__init__.py +30 -0
  38. pyweb_stack-0.5.0/pyweb/jobs/builtin.py +51 -0
  39. pyweb_stack-0.5.0/pyweb/jobs/core.py +265 -0
  40. pyweb_stack-0.5.0/pyweb/jobs/db.py +286 -0
  41. pyweb_stack-0.4.4/pyweb/jobs.py → pyweb_stack-0.5.0/pyweb/jobs/legacy.py +10 -3
  42. pyweb_stack-0.5.0/pyweb/jobs/memory.py +215 -0
  43. pyweb_stack-0.5.0/pyweb/jobs/redis.py +264 -0
  44. pyweb_stack-0.5.0/pyweb/jobs/schedule.py +179 -0
  45. pyweb_stack-0.5.0/pyweb/jobs/worker.py +407 -0
  46. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/keys.py +4 -5
  47. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/livedata.py +100 -15
  48. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/lsp.py +135 -8
  49. pyweb_stack-0.5.0/pyweb/lsp_data.py +326 -0
  50. pyweb_stack-0.5.0/pyweb/mail.py +184 -0
  51. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/mcp.py +263 -7
  52. pyweb_stack-0.5.0/pyweb/models/__init__.py +1067 -0
  53. pyweb_stack-0.5.0/pyweb/models/fields.py +479 -0
  54. pyweb_stack-0.5.0/pyweb/models/query.py +663 -0
  55. pyweb_stack-0.5.0/pyweb/models/relations.py +670 -0
  56. pyweb_stack-0.5.0/pyweb/net/__init__.py +1 -0
  57. pyweb_stack-0.5.0/pyweb/net/http.py +233 -0
  58. pyweb_stack-0.5.0/pyweb/net/live.py +441 -0
  59. pyweb_stack-0.5.0/pyweb/net/server.py +557 -0
  60. pyweb_stack-0.5.0/pyweb/net/ws.py +253 -0
  61. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/packages.py +1 -1
  62. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/realtime.py +31 -5
  63. pyweb_stack-0.5.0/pyweb/rooms.py +98 -0
  64. pyweb_stack-0.5.0/pyweb/rules.py +113 -0
  65. pyweb_stack-0.5.0/pyweb/runtime/browser/auth.js +97 -0
  66. pyweb_stack-0.5.0/pyweb/runtime/browser/forms.js +252 -0
  67. pyweb_stack-0.5.0/pyweb/runtime/browser/live.js +167 -0
  68. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/runtime/browser/runtime.js +0 -53
  69. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/runtime/server/__init__.py +322 -13
  70. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/runtime/server/argcheck.py +47 -2
  71. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/security.py +70 -3
  72. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/serve.py +34 -12
  73. pyweb_stack-0.5.0/pyweb/storage.py +260 -0
  74. pyweb_stack-0.5.0/pyweb/telemetry/__init__.py +200 -0
  75. pyweb_stack-0.5.0/pyweb/telemetry/devtools.py +187 -0
  76. pyweb_stack-0.5.0/pyweb/telemetry/instruments.py +74 -0
  77. pyweb_stack-0.5.0/pyweb/telemetry/logs.py +139 -0
  78. pyweb_stack-0.5.0/pyweb/telemetry/metrics.py +285 -0
  79. pyweb_stack-0.5.0/pyweb/telemetry/tracing.py +270 -0
  80. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/blog.pyweb +11 -20
  81. pyweb_stack-0.5.0/pyweb/templates/saas/app.pyweb +168 -0
  82. pyweb_stack-0.5.0/pyweb/templates/saas/seeds.py +14 -0
  83. pyweb_stack-0.5.0/pyweb/templates/saas/test_app.py +67 -0
  84. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/testing.py +90 -28
  85. pyweb_stack-0.5.0/pyweb/upgrade.py +187 -0
  86. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb_stack.egg-info/PKG-INFO +142 -35
  87. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb_stack.egg-info/SOURCES.txt +67 -3
  88. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb_stack.egg-info/requires.txt +13 -0
  89. pyweb_stack-0.5.0/tests/test_authkit.py +650 -0
  90. pyweb_stack-0.5.0/tests/test_authkit_units.py +286 -0
  91. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_cli_deploy.py +19 -12
  92. pyweb_stack-0.5.0/tests/test_cron.py +199 -0
  93. pyweb_stack-0.5.0/tests/test_data_layer.py +702 -0
  94. pyweb_stack-0.5.0/tests/test_deploy_v5.py +305 -0
  95. pyweb_stack-0.5.0/tests/test_dx_v5.py +504 -0
  96. pyweb_stack-0.5.0/tests/test_forms_v5.py +448 -0
  97. pyweb_stack-0.5.0/tests/test_jobs_app.py +294 -0
  98. pyweb_stack-0.5.0/tests/test_jobs_v5.py +387 -0
  99. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_keys_sessions.py +3 -2
  100. pyweb_stack-0.5.0/tests/test_live_patches.py +93 -0
  101. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_lsp.py +8 -0
  102. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_mcp.py +7 -1
  103. pyweb_stack-0.5.0/tests/test_migrations_property.py +98 -0
  104. pyweb_stack-0.5.0/tests/test_migrations_v5.py +392 -0
  105. pyweb_stack-0.5.0/tests/test_net.py +637 -0
  106. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_production_gaps.py +2 -1
  107. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_realtime_transport.py +5 -3
  108. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_rpc_production.py +3 -2
  109. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_server_db.py +6 -1
  110. pyweb_stack-0.5.0/tests/test_telemetry.py +536 -0
  111. pyweb_stack-0.4.4/pyweb/app.py +0 -78
  112. pyweb_stack-0.4.4/pyweb/cli/__init__.py +0 -553
  113. pyweb_stack-0.4.4/pyweb/db/migrate.py +0 -143
  114. pyweb_stack-0.4.4/pyweb/decorators.py +0 -49
  115. pyweb_stack-0.4.4/pyweb/forms.py +0 -108
  116. pyweb_stack-0.4.4/pyweb/models.py +0 -268
  117. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/LICENSE +0 -0
  118. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/bench.py +0 -0
  119. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/browser.py +0 -0
  120. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/cache.py +0 -0
  121. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/cli/__main__.py +0 -0
  122. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/__init__.py +0 -0
  123. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/ast.py +0 -0
  124. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/codegen/__init__.py +0 -0
  125. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/codegen/ir.py +0 -0
  126. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/errors.py +0 -0
  127. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/parser.py +0 -0
  128. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/pyjs.py +0 -0
  129. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/rpc.py +0 -0
  130. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/css.py +0 -0
  131. /pyweb_stack-0.4.4/pyweb/deploy.py → /pyweb_stack-0.5.0/pyweb/deploy/legacy.py +0 -0
  132. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/dts.py +0 -0
  133. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/livetable.py +0 -0
  134. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/markdown.py +0 -0
  135. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/observability.py +0 -0
  136. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/platform.py +0 -0
  137. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/plugins.py +0 -0
  138. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/py.typed +0 -0
  139. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/rpc.py +0 -0
  140. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/runtime/browser/markdown.js +0 -0
  141. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/ssr.py +0 -0
  142. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/sync.py +0 -0
  143. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/ai-chat.pyweb +0 -0
  144. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/app.css +0 -0
  145. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/auth.pyweb +0 -0
  146. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/chat.pyweb +0 -0
  147. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/counter.pyweb +0 -0
  148. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/todo.pyweb +0 -0
  149. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/uploads.py +0 -0
  150. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb_stack.egg-info/dependency_links.txt +0 -0
  151. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb_stack.egg-info/entry_points.txt +0 -0
  152. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb_stack.egg-info/top_level.txt +0 -0
  153. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/setup.cfg +0 -0
  154. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_auth.py +0 -0
  155. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_auth_contract.py +0 -0
  156. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_auth_security.py +0 -0
  157. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_auth_v1.py +0 -0
  158. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_backplane.py +0 -0
  159. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_browser_build.py +0 -0
  160. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_budget.py +0 -0
  161. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_codegen.py +0 -0
  162. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_compiler.py +0 -0
  163. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_db_production.py +0 -0
  164. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_deploy_upload_render.py +0 -0
  165. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_docs.py +0 -0
  166. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_dts.py +0 -0
  167. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_e2e.py +0 -0
  168. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_examples.py +0 -0
  169. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_forms_security_obs.py +0 -0
  170. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_jobs_cache_realtime.py +0 -0
  171. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_livedata.py +0 -0
  172. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_markdown.py +0 -0
  173. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_models_db.py +0 -0
  174. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_multifile.py +0 -0
  175. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_multipage.py +0 -0
  176. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_observability.py +0 -0
  177. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_packages.py +0 -0
  178. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_parser.py +0 -0
  179. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_parser_v1.py +0 -0
  180. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_plugins_platform_bench.py +0 -0
  181. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_pyjs_semantics.py +0 -0
  182. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_reactivity.py +0 -0
  183. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_reference_apps.py +0 -0
  184. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_request_hardening.py +0 -0
  185. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_rpc_placement.py +0 -0
  186. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_runtime_signals.py +0 -0
  187. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_static_hashing.py +0 -0
  188. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_static_serving.py +0 -0
  189. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_streaming.py +0 -0
  190. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_sync_live.py +0 -0
  191. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_systems.py +0 -0
  192. {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_website.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyweb-stack
3
- Version: 0.4.4
3
+ Version: 0.5.0
4
4
  Summary: Full-stack web apps in one Python file: server-rendered pages, reactive browser UI, typed RPC.
5
5
  Author-email: MaanavKrishna <67054795+MaanavKrishna@users.noreply.github.com>
6
6
  Maintainer-email: MaanavKrishna <67054795+MaanavKrishna@users.noreply.github.com>
@@ -40,16 +40,27 @@ Provides-Extra: crypto
40
40
  Requires-Dist: cryptography>=42; extra == "crypto"
41
41
  Provides-Extra: asgi
42
42
  Requires-Dist: uvicorn>=0.30; extra == "asgi"
43
+ Provides-Extra: auth
44
+ Requires-Dist: argon2-cffi>=23.1; extra == "auth"
45
+ Provides-Extra: otel
46
+ Requires-Dist: opentelemetry-sdk>=1.24; extra == "otel"
47
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.24; extra == "otel"
43
48
  Provides-Extra: all
44
49
  Requires-Dist: psycopg[binary]>=3.1; extra == "all"
45
50
  Requires-Dist: PyMySQL>=1.1; extra == "all"
46
51
  Requires-Dist: redis>=5; extra == "all"
47
52
  Requires-Dist: cryptography>=42; extra == "all"
48
53
  Requires-Dist: uvicorn>=0.30; extra == "all"
54
+ Requires-Dist: argon2-cffi>=23.1; extra == "all"
55
+ Requires-Dist: opentelemetry-sdk>=1.24; extra == "all"
56
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.24; extra == "all"
49
57
  Provides-Extra: test
50
58
  Requires-Dist: pytest>=8; extra == "test"
51
59
  Requires-Dist: playwright>=1.45; extra == "test"
52
60
  Requires-Dist: mcp>=1.2; extra == "test"
61
+ Requires-Dist: hypothesis>=6.100; extra == "test"
62
+ Requires-Dist: pyyaml>=6; extra == "test"
63
+ Requires-Dist: opentelemetry-sdk>=1.24; extra == "test"
53
64
  Dynamic: license-file
54
65
 
55
66
  <div align="center">
@@ -59,7 +70,8 @@ Dynamic: license-file
59
70
  **Full-stack web apps in one Python file.**
60
71
 
61
72
  Server-rendered pages, a reactive UI compiled from Python, typed calls to
62
- your server, live data and streaming AI, with no JavaScript toolchain.
73
+ your server, Models with migrations, forms, accounts, live updates,
74
+ background jobs and one-command deploys, with no JavaScript toolchain.
63
75
 
64
76
  [![CI](https://github.com/MaanavKrishna/PyWeb/actions/workflows/ci.yml/badge.svg)](https://github.com/MaanavKrishna/PyWeb/actions/workflows/ci.yml)
65
77
  [![PyPI](https://img.shields.io/pypi/v/pyweb-stack)](https://pypi.org/project/pyweb-stack/)
@@ -124,6 +136,63 @@ a validated JSON endpoint the browser calls for you.
124
136
 
125
137
  ## What you get
126
138
 
139
+ ### A whole product in one file
140
+
141
+ ```pyweb
142
+ from pyweb import App, Field, Form, Input, Model, Submit, server
143
+ from pyweb.authkit import User
144
+
145
+ app = App(title="Feedback", database="sqlite:///app.db")
146
+ auth = app.use_auth() # sign-up, passkeys, OAuth, /admin
147
+
148
+
149
+ class Idea(Model):
150
+ text: str = Field(min=3, max=280)
151
+ votes: int = 0
152
+ author: User = Field(readonly=True)
153
+
154
+
155
+ def own(user, idea):
156
+ return user is not None and idea.author_id == user.id
157
+
158
+
159
+ Idea.policy(write=own) # only its author can change an idea
160
+
161
+
162
+ @server(login=True)
163
+ def share(idea: Idea): # the form's fields come from Idea
164
+ idea.author = auth.user()
165
+ idea.save()
166
+ thank.enqueue(idea.id) # runs after the request commits
167
+
168
+
169
+ @app.job(retries=3)
170
+ def thank(idea_id: int):
171
+ print(f"thanks for idea {idea_id}")
172
+
173
+
174
+ @app.page("/", login=True)
175
+ def Ideas():
176
+ ideas = Idea.query().order("-id").live() # every open tab
177
+
178
+ <Form action={share}>
179
+ <Input name="text" placeholder="Your idea" />
180
+ <Submit>Share</Submit>
181
+ </Form>
182
+ <ul>
183
+ for idea in ideas:
184
+ <li>{idea["text"]} ({idea["votes"]})</li>
185
+ </ul>
186
+ ```
187
+
188
+ That's accounts (sign-up with email confirmation, passkeys, OAuth, an
189
+ admin), a table with rules and a row policy, a form that checks what
190
+ people type in the browser and again on the server (and works without
191
+ JavaScript), a background job queued in the same transaction, and a list
192
+ that updates in every open tab when anyone shares an idea. Start a bigger
193
+ one with `pyweb new myapp --template saas`, or follow
194
+ [a SaaS in an hour](docs/28-saas-tutorial.md).
195
+
127
196
  ### Python on both sides, split by the compiler
128
197
 
129
198
  You write one file. The compiler decides, line by line, what runs in the
@@ -133,19 +202,22 @@ everything else is computed on the server. Database handles, imports and
133
202
  secrets can't leak into browser code: that's a compile error with a line
134
203
  number, not a production incident.
135
204
 
136
- ### Pages that follow the database
205
+ ### Data, with migrations that write themselves
137
206
 
138
207
  ```pyweb
139
- from pyweb import App, live
140
- from pyweb.db import connect
208
+ from pyweb import App, Field, Model
141
209
 
142
- app = App()
143
- db = connect("sqlite:///shop.db")
210
+ app = App(database="sqlite:///shop.db") # DATABASE_URL takes over in production
211
+
212
+
213
+ class Order(Model):
214
+ item: str = Field(max=80)
215
+ status: str = Field(default="new", choices=["new", "paid", "shipped"])
144
216
 
145
217
 
146
218
  @app.page("/orders")
147
219
  def Orders():
148
- orders = live(db, "select item, status from orders order by id desc limit 20")
220
+ orders = Order.query().order("-id").limit(20).live()
149
221
 
150
222
  <ul>
151
223
  for o in orders:
@@ -153,8 +225,14 @@ def Orders():
153
225
  </ul>
154
226
  ```
155
227
 
156
- Any write through `pyweb.db` updates every open window. Identical queries
157
- run once for all viewers, and it works across processes with Redis.
228
+ Models work on SQLite, Postgres and MySQL, with relations loaded by
229
+ `include()` (an N+1 loop is an error while you develop), cursor
230
+ pagination, transactions per request and read replicas.
231
+ `pyweb db diff` writes the migration when a Model changes and
232
+ `pyweb db upgrade` applies it, under a lock so many servers can start at
233
+ once. `.live()` keeps the list in step with the database in every open
234
+ tab: when one row changes, one row is sent. Raw SQL works too
235
+ (`live(db, "select ...")`). [Data →](docs/09-data.md) ·
158
236
  [Live data →](docs/21-live-data.md)
159
237
 
160
238
  ### AI features that stream
@@ -244,17 +322,36 @@ keeps only the files the browser loads and pins them in `pyweb.lock`.
244
322
  There's no `node_modules` and no bundler, and pages that don't use a
245
323
  package don't load it. [npm packages →](docs/18-npm-packages.md)
246
324
 
325
+ ### Ship it, then see what it does
326
+
327
+ ```bash
328
+ pyweb deploy fly # or docker, compose, k8s, render, railway
329
+ ```
330
+
331
+ `pyweb deploy` reads your app and explains its plan: Postgres instead of
332
+ SQLite, a worker for jobs, Redis when there's more than one server,
333
+ migrations before the new version takes traffic. Then it writes the
334
+ files. In production every log line is JSON with the request id and
335
+ trace id, `/metrics` answers Prometheus, OpenTelemetry traces follow a
336
+ click into its server function, SQL and background job, and while you
337
+ develop a toolbar on every page shows each request's queries and flags
338
+ N+1 loops. [Scaling & deploying →](docs/25-scaling.md) ·
339
+ [Logs, metrics & tracing →](docs/26-observability.md)
340
+
247
341
  ### Small, fast and boring to run
248
342
 
249
343
  - **Every page is server-rendered**: complete HTML on first paint, then
250
344
  hydrated in place. Pages without interactivity ship no JavaScript.
251
345
  - **Small**: a typical interactive page is under 1 KB of code plus a
252
- ~15 KB (gzip) runtime that's cached across pages.
253
- - **Stateless servers**: signed-cookie sessions and plain JSON over HTTP
254
- scale behind any load balancer. Run `pyweb serve`, uvicorn/gunicorn
255
- (ASGI) or the generated Dockerfile.
256
- - **Secure defaults**: escaped output, a strict Content Security Policy,
257
- CSRF and origin checks on RPC, rate limits and request size limits.
346
+ ~14 KB (gzip) runtime that's cached across pages. Forms and live
347
+ updates add a few KB, only on pages that use them.
348
+ - **Its own server**: `pyweb serve` handles HTTP/1.1 keep-alive and
349
+ WebSockets on one asyncio loop per process, with `--workers N`
350
+ processes. Sessions are signed cookies, so servers stay stateless
351
+ behind any load balancer. uvicorn and other ASGI servers work too.
352
+ - **Secure defaults**: escaped output, a hashed Content Security Policy,
353
+ CSRF and origin checks, row policies, rate limits, request size limits
354
+ and `pyweb check --production` before you deploy.
258
355
 
259
356
  ## See it
260
357
 
@@ -270,7 +367,7 @@ it runs the real PyWeb, server functions included, in your browser.
270
367
 
271
368
  ```bash
272
369
  pip install pyweb-stack
273
- pyweb new myapp --template todo # blank | counter | todo | blog | auth | chat | ai-chat
370
+ pyweb new myapp --template saas # saas | blank | counter | todo | blog | auth | chat | ai-chat
274
371
  cd myapp
275
372
  pyweb dev app.pyweb # edit app.pyweb; the page reloads on save
276
373
  ```
@@ -280,8 +377,8 @@ Then:
280
377
  ```bash
281
378
  pyweb check app.pyweb # compile + security checks (CI-friendly exit codes)
282
379
  pytest # new apps come with test_app.py
283
- pyweb build app.pyweb --production # hashed, minified, self-contained dist/
284
- pyweb serve dist # production server
380
+ pyweb db diff # a migration for what changed in your Models
381
+ pyweb deploy compose # or fly, render, railway, k8s, docker
285
382
  ```
286
383
 
287
384
  The [quickstart](docs/02-quickstart.md) and [tutorial](docs/03-tutorial.md)
@@ -301,7 +398,7 @@ take it from there.
301
398
  │ handlers in JS │ │ sessions, secrets │
302
399
  └────────┬────────┘ └────────────┬────────────┘
303
400
  └──── typed JSON RPC ────┘
304
- (streams and live updates over the same HTTP)
401
+ (streams over HTTP; live data over one WebSocket per tab)
305
402
  ```
306
403
 
307
404
  The [architecture overview](ARCHITECTURE.md) goes through each stage.
@@ -309,8 +406,10 @@ The [architecture overview](ARCHITECTURE.md) goes through each stage.
309
406
  ## Build it with AI
310
407
 
311
408
  PyWeb ships an MCP server so AI assistants can scaffold, check, inspect,
312
- render, screenshot and test your app, add npm packages, and map its
313
- routes. Errors come back as line numbers with fix hints:
409
+ render, screenshot and test your app, add npm packages, map its routes,
410
+ and look at its data: the schema, read-only queries (row-limited, secrets
411
+ redacted), pending migrations, jobs and recent requests. Errors come back
412
+ as line numbers with fix hints:
314
413
 
315
414
  ```bash
316
415
  claude mcp add pyweb -- pyweb mcp # Claude Code
@@ -329,8 +428,11 @@ site publishes [`llms-full.txt`](https://maanavkrishna.github.io/PyWeb/llms-full
329
428
 
330
429
  The **[VS Code extension](editors/vscode)** adds highlighting, errors as
331
430
  you type, hover that shows where code runs (and the signatures of npm
332
- packages), completion and go to definition. Any other LSP editor can run
333
- `pyweb lsp` ([setup](docs/14-cli.md#editor-support)).
431
+ packages), completion that knows your Models' fields and query methods,
432
+ warnings for N+1 loops and form fields the action doesn't take, a code
433
+ lens that writes the migration when a Model changes, and go to
434
+ definition. Any other LSP editor can run `pyweb lsp`
435
+ ([setup](docs/14-cli.md#editor-support)).
334
436
 
335
437
  ## Examples
336
438
 
@@ -339,10 +441,11 @@ real browser by the test suite.
339
441
 
340
442
  | Example | Shows |
341
443
  |---|---|
444
+ | [`saas`](examples/saas/app.pyweb) | accounts, Models with migrations, row policies, forms, live lists, a job, a daily email, an admin |
342
445
  | [`counter`](examples/counter/app.pyweb) | signals, computed values, binding a number input |
343
446
  | [`todo`](examples/todo/app.pyweb) | components, list mutation, filters, keyed lists |
344
- | [`blog`](examples/blog/app.pyweb) | SQL database, server functions, route parameters, 404s, validation errors |
345
- | [`auth`](examples/auth/app.pyweb) | registration, password hashing, sessions, protected pages |
447
+ | [`blog`](examples/blog/app.pyweb) | a Model, server functions, route parameters, 404s, validation errors |
448
+ | [`auth`](examples/auth/app.pyweb) | your own registration, password hashing and sessions (or use `app.use_auth()`) |
346
449
  | [`chat`](examples/chat/app.pyweb) | route parameters, shared server state, live updates with `publish`/`subscribe` |
347
450
  | [`showcase`](examples/showcase/app.pyweb) | everything on one page, with a stylesheet |
348
451
  | [`site`](examples/site/app.pyweb) | a layout, client-side navigation, query parameters, page titles, a 404 page |
@@ -353,10 +456,10 @@ real browser by the test suite.
353
456
 
354
457
  | | |
355
458
  |---|---|
356
- | Start | [Introduction](docs/01-introduction.md) · [Quickstart](docs/02-quickstart.md) · [Tutorial](docs/03-tutorial.md) · [AI assistants & MCP](docs/17-ai-assistants.md) |
459
+ | Start | [Introduction](docs/01-introduction.md) · [Quickstart](docs/02-quickstart.md) · [Tutorial](docs/03-tutorial.md) · [A SaaS in an hour](docs/28-saas-tutorial.md) · [AI assistants & MCP](docs/17-ai-assistants.md) |
357
460
  | 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) |
358
- | 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) |
359
- | Ship | [Testing](docs/11-testing.md) · [Deployment](docs/12-deployment.md) · [Security](docs/13-security.md) · [CLI](docs/14-cli.md) |
461
+ | 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) · [Forms](docs/23-forms.md) · [Auth](docs/10-auth.md) · [Live data](docs/21-live-data.md) · [Background jobs](docs/24-jobs.md) · [Building AI apps](docs/20-ai-apps.md) |
462
+ | Ship | [Testing](docs/11-testing.md) · [Deployment](docs/12-deployment.md) · [Scaling](docs/25-scaling.md) · [Logs, metrics & tracing](docs/26-observability.md) · [Security](docs/13-security.md) · [CLI](docs/14-cli.md) · [Upgrading to 0.5](docs/27-upgrading.md) |
360
463
  | 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) |
361
464
 
362
465
  The same docs are published at
@@ -384,17 +487,21 @@ Upgrade with `pip install -U pyweb-stack`.
384
487
 
385
488
  | Version | Highlights |
386
489
  |---|---|
490
+ | 0.5 | Models with migrations, forms, auth kit with passkeys, OAuth and an admin, row-level live updates over WebSockets on PyWeb's own server, durable jobs and schedules, `pyweb deploy` for docker, compose, k8s, fly, render and railway, logs, metrics, tracing and a dev toolbar, data-aware editor and MCP tools, the `saas` template, `pyweb upgrade` |
387
491
  | 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; 0.4.1: richer MCP tools; 0.4.2: VS Code run, new-app and MCP commands; 0.4.3: fixes, security hardening, gzip and caching; 0.4.4: key rotation, session revocation, typed arguments, Redis-shared limits, `check --production` |
388
492
  | 0.3 | Hydration, live updates (SSE), multi-file apps, language server + VS Code extension, browser playground, faster rendering, screenshot/test MCP tools |
389
493
  | 0.2 | MCP server for AI assistants, AI guide, project templates, `AGENTS.md`/`CLAUDE.md`, `llms.txt` |
390
494
  | 0.1 | First public release: compiler, reactive runtime, server rendering, typed RPC, sessions, databases, CLI |
391
495
 
392
- The test suite covers the parser, the Python→JavaScript translation
393
- (differentially, against CPython), the reactive runtime, server
394
- rendering, RPC, streaming, live queries, client-side navigation, every
395
- example app in Chromium, the playground on Pyodide, and the
396
- database/Redis layers against real Postgres, MySQL and Redis servers, on
397
- Python 3.10–3.13.
496
+ The test suite (about 1,200 tests) covers the parser, the
497
+ Python→JavaScript translation (differentially, against CPython), the
498
+ reactive runtime, server rendering, RPC, forms, Models and migrations
499
+ (with property tests), the HTTP and WebSocket parsers (fuzzed), live
500
+ patches, jobs on every backend, every example app in Chromium, the
501
+ playground on Pyodide, and the whole suite against real Postgres, MySQL
502
+ and Redis servers, on Python 3.10–3.13. CI also boots a
503
+ `pyweb deploy compose` stack and runs the VS Code extension in a real
504
+ VS Code.
398
505
 
399
506
  ## Contributing
400
507
 
@@ -5,7 +5,8 @@
5
5
  **Full-stack web apps in one Python file.**
6
6
 
7
7
  Server-rendered pages, a reactive UI compiled from Python, typed calls to
8
- your server, live data and streaming AI, with no JavaScript toolchain.
8
+ your server, Models with migrations, forms, accounts, live updates,
9
+ background jobs and one-command deploys, with no JavaScript toolchain.
9
10
 
10
11
  [![CI](https://github.com/MaanavKrishna/PyWeb/actions/workflows/ci.yml/badge.svg)](https://github.com/MaanavKrishna/PyWeb/actions/workflows/ci.yml)
11
12
  [![PyPI](https://img.shields.io/pypi/v/pyweb-stack)](https://pypi.org/project/pyweb-stack/)
@@ -70,6 +71,63 @@ a validated JSON endpoint the browser calls for you.
70
71
 
71
72
  ## What you get
72
73
 
74
+ ### A whole product in one file
75
+
76
+ ```pyweb
77
+ from pyweb import App, Field, Form, Input, Model, Submit, server
78
+ from pyweb.authkit import User
79
+
80
+ app = App(title="Feedback", database="sqlite:///app.db")
81
+ auth = app.use_auth() # sign-up, passkeys, OAuth, /admin
82
+
83
+
84
+ class Idea(Model):
85
+ text: str = Field(min=3, max=280)
86
+ votes: int = 0
87
+ author: User = Field(readonly=True)
88
+
89
+
90
+ def own(user, idea):
91
+ return user is not None and idea.author_id == user.id
92
+
93
+
94
+ Idea.policy(write=own) # only its author can change an idea
95
+
96
+
97
+ @server(login=True)
98
+ def share(idea: Idea): # the form's fields come from Idea
99
+ idea.author = auth.user()
100
+ idea.save()
101
+ thank.enqueue(idea.id) # runs after the request commits
102
+
103
+
104
+ @app.job(retries=3)
105
+ def thank(idea_id: int):
106
+ print(f"thanks for idea {idea_id}")
107
+
108
+
109
+ @app.page("/", login=True)
110
+ def Ideas():
111
+ ideas = Idea.query().order("-id").live() # every open tab
112
+
113
+ <Form action={share}>
114
+ <Input name="text" placeholder="Your idea" />
115
+ <Submit>Share</Submit>
116
+ </Form>
117
+ <ul>
118
+ for idea in ideas:
119
+ <li>{idea["text"]} ({idea["votes"]})</li>
120
+ </ul>
121
+ ```
122
+
123
+ That's accounts (sign-up with email confirmation, passkeys, OAuth, an
124
+ admin), a table with rules and a row policy, a form that checks what
125
+ people type in the browser and again on the server (and works without
126
+ JavaScript), a background job queued in the same transaction, and a list
127
+ that updates in every open tab when anyone shares an idea. Start a bigger
128
+ one with `pyweb new myapp --template saas`, or follow
129
+ [a SaaS in an hour](docs/28-saas-tutorial.md).
130
+
73
131
  ### Python on both sides, split by the compiler
74
132
 
75
133
  You write one file. The compiler decides, line by line, what runs in the
@@ -79,19 +137,22 @@ everything else is computed on the server. Database handles, imports and
79
137
  secrets can't leak into browser code: that's a compile error with a line
80
138
  number, not a production incident.
81
139
 
82
- ### Pages that follow the database
140
+ ### Data, with migrations that write themselves
83
141
 
84
142
  ```pyweb
85
- from pyweb import App, live
86
- from pyweb.db import connect
143
+ from pyweb import App, Field, Model
87
144
 
88
- app = App()
89
- db = connect("sqlite:///shop.db")
145
+ app = App(database="sqlite:///shop.db") # DATABASE_URL takes over in production
146
+
147
+
148
+ class Order(Model):
149
+ item: str = Field(max=80)
150
+ status: str = Field(default="new", choices=["new", "paid", "shipped"])
90
151
 
91
152
 
92
153
  @app.page("/orders")
93
154
  def Orders():
94
- orders = live(db, "select item, status from orders order by id desc limit 20")
155
+ orders = Order.query().order("-id").limit(20).live()
95
156
 
96
157
  <ul>
97
158
  for o in orders:
@@ -99,8 +160,14 @@ def Orders():
99
160
  </ul>
100
161
  ```
101
162
 
102
- Any write through `pyweb.db` updates every open window. Identical queries
103
- run once for all viewers, and it works across processes with Redis.
163
+ Models work on SQLite, Postgres and MySQL, with relations loaded by
164
+ `include()` (an N+1 loop is an error while you develop), cursor
165
+ pagination, transactions per request and read replicas.
166
+ `pyweb db diff` writes the migration when a Model changes and
167
+ `pyweb db upgrade` applies it, under a lock so many servers can start at
168
+ once. `.live()` keeps the list in step with the database in every open
169
+ tab: when one row changes, one row is sent. Raw SQL works too
170
+ (`live(db, "select ...")`). [Data →](docs/09-data.md) ·
104
171
  [Live data →](docs/21-live-data.md)
105
172
 
106
173
  ### AI features that stream
@@ -190,17 +257,36 @@ keeps only the files the browser loads and pins them in `pyweb.lock`.
190
257
  There's no `node_modules` and no bundler, and pages that don't use a
191
258
  package don't load it. [npm packages →](docs/18-npm-packages.md)
192
259
 
260
+ ### Ship it, then see what it does
261
+
262
+ ```bash
263
+ pyweb deploy fly # or docker, compose, k8s, render, railway
264
+ ```
265
+
266
+ `pyweb deploy` reads your app and explains its plan: Postgres instead of
267
+ SQLite, a worker for jobs, Redis when there's more than one server,
268
+ migrations before the new version takes traffic. Then it writes the
269
+ files. In production every log line is JSON with the request id and
270
+ trace id, `/metrics` answers Prometheus, OpenTelemetry traces follow a
271
+ click into its server function, SQL and background job, and while you
272
+ develop a toolbar on every page shows each request's queries and flags
273
+ N+1 loops. [Scaling & deploying →](docs/25-scaling.md) ·
274
+ [Logs, metrics & tracing →](docs/26-observability.md)
275
+
193
276
  ### Small, fast and boring to run
194
277
 
195
278
  - **Every page is server-rendered**: complete HTML on first paint, then
196
279
  hydrated in place. Pages without interactivity ship no JavaScript.
197
280
  - **Small**: a typical interactive page is under 1 KB of code plus a
198
- ~15 KB (gzip) runtime that's cached across pages.
199
- - **Stateless servers**: signed-cookie sessions and plain JSON over HTTP
200
- scale behind any load balancer. Run `pyweb serve`, uvicorn/gunicorn
201
- (ASGI) or the generated Dockerfile.
202
- - **Secure defaults**: escaped output, a strict Content Security Policy,
203
- CSRF and origin checks on RPC, rate limits and request size limits.
281
+ ~14 KB (gzip) runtime that's cached across pages. Forms and live
282
+ updates add a few KB, only on pages that use them.
283
+ - **Its own server**: `pyweb serve` handles HTTP/1.1 keep-alive and
284
+ WebSockets on one asyncio loop per process, with `--workers N`
285
+ processes. Sessions are signed cookies, so servers stay stateless
286
+ behind any load balancer. uvicorn and other ASGI servers work too.
287
+ - **Secure defaults**: escaped output, a hashed Content Security Policy,
288
+ CSRF and origin checks, row policies, rate limits, request size limits
289
+ and `pyweb check --production` before you deploy.
204
290
 
205
291
  ## See it
206
292
 
@@ -216,7 +302,7 @@ it runs the real PyWeb, server functions included, in your browser.
216
302
 
217
303
  ```bash
218
304
  pip install pyweb-stack
219
- pyweb new myapp --template todo # blank | counter | todo | blog | auth | chat | ai-chat
305
+ pyweb new myapp --template saas # saas | blank | counter | todo | blog | auth | chat | ai-chat
220
306
  cd myapp
221
307
  pyweb dev app.pyweb # edit app.pyweb; the page reloads on save
222
308
  ```
@@ -226,8 +312,8 @@ Then:
226
312
  ```bash
227
313
  pyweb check app.pyweb # compile + security checks (CI-friendly exit codes)
228
314
  pytest # new apps come with test_app.py
229
- pyweb build app.pyweb --production # hashed, minified, self-contained dist/
230
- pyweb serve dist # production server
315
+ pyweb db diff # a migration for what changed in your Models
316
+ pyweb deploy compose # or fly, render, railway, k8s, docker
231
317
  ```
232
318
 
233
319
  The [quickstart](docs/02-quickstart.md) and [tutorial](docs/03-tutorial.md)
@@ -247,7 +333,7 @@ take it from there.
247
333
  │ handlers in JS │ │ sessions, secrets │
248
334
  └────────┬────────┘ └────────────┬────────────┘
249
335
  └──── typed JSON RPC ────┘
250
- (streams and live updates over the same HTTP)
336
+ (streams over HTTP; live data over one WebSocket per tab)
251
337
  ```
252
338
 
253
339
  The [architecture overview](ARCHITECTURE.md) goes through each stage.
@@ -255,8 +341,10 @@ The [architecture overview](ARCHITECTURE.md) goes through each stage.
255
341
  ## Build it with AI
256
342
 
257
343
  PyWeb ships an MCP server so AI assistants can scaffold, check, inspect,
258
- render, screenshot and test your app, add npm packages, and map its
259
- routes. Errors come back as line numbers with fix hints:
344
+ render, screenshot and test your app, add npm packages, map its routes,
345
+ and look at its data: the schema, read-only queries (row-limited, secrets
346
+ redacted), pending migrations, jobs and recent requests. Errors come back
347
+ as line numbers with fix hints:
260
348
 
261
349
  ```bash
262
350
  claude mcp add pyweb -- pyweb mcp # Claude Code
@@ -275,8 +363,11 @@ site publishes [`llms-full.txt`](https://maanavkrishna.github.io/PyWeb/llms-full
275
363
 
276
364
  The **[VS Code extension](editors/vscode)** adds highlighting, errors as
277
365
  you type, hover that shows where code runs (and the signatures of npm
278
- packages), completion and go to definition. Any other LSP editor can run
279
- `pyweb lsp` ([setup](docs/14-cli.md#editor-support)).
366
+ packages), completion that knows your Models' fields and query methods,
367
+ warnings for N+1 loops and form fields the action doesn't take, a code
368
+ lens that writes the migration when a Model changes, and go to
369
+ definition. Any other LSP editor can run `pyweb lsp`
370
+ ([setup](docs/14-cli.md#editor-support)).
280
371
 
281
372
  ## Examples
282
373
 
@@ -285,10 +376,11 @@ real browser by the test suite.
285
376
 
286
377
  | Example | Shows |
287
378
  |---|---|
379
+ | [`saas`](examples/saas/app.pyweb) | accounts, Models with migrations, row policies, forms, live lists, a job, a daily email, an admin |
288
380
  | [`counter`](examples/counter/app.pyweb) | signals, computed values, binding a number input |
289
381
  | [`todo`](examples/todo/app.pyweb) | components, list mutation, filters, keyed lists |
290
- | [`blog`](examples/blog/app.pyweb) | SQL database, server functions, route parameters, 404s, validation errors |
291
- | [`auth`](examples/auth/app.pyweb) | registration, password hashing, sessions, protected pages |
382
+ | [`blog`](examples/blog/app.pyweb) | a Model, server functions, route parameters, 404s, validation errors |
383
+ | [`auth`](examples/auth/app.pyweb) | your own registration, password hashing and sessions (or use `app.use_auth()`) |
292
384
  | [`chat`](examples/chat/app.pyweb) | route parameters, shared server state, live updates with `publish`/`subscribe` |
293
385
  | [`showcase`](examples/showcase/app.pyweb) | everything on one page, with a stylesheet |
294
386
  | [`site`](examples/site/app.pyweb) | a layout, client-side navigation, query parameters, page titles, a 404 page |
@@ -299,10 +391,10 @@ real browser by the test suite.
299
391
 
300
392
  | | |
301
393
  |---|---|
302
- | Start | [Introduction](docs/01-introduction.md) · [Quickstart](docs/02-quickstart.md) · [Tutorial](docs/03-tutorial.md) · [AI assistants & MCP](docs/17-ai-assistants.md) |
394
+ | Start | [Introduction](docs/01-introduction.md) · [Quickstart](docs/02-quickstart.md) · [Tutorial](docs/03-tutorial.md) · [A SaaS in an hour](docs/28-saas-tutorial.md) · [AI assistants & MCP](docs/17-ai-assistants.md) |
303
395
  | 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) |
304
- | 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) |
305
- | Ship | [Testing](docs/11-testing.md) · [Deployment](docs/12-deployment.md) · [Security](docs/13-security.md) · [CLI](docs/14-cli.md) |
396
+ | 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) · [Forms](docs/23-forms.md) · [Auth](docs/10-auth.md) · [Live data](docs/21-live-data.md) · [Background jobs](docs/24-jobs.md) · [Building AI apps](docs/20-ai-apps.md) |
397
+ | Ship | [Testing](docs/11-testing.md) · [Deployment](docs/12-deployment.md) · [Scaling](docs/25-scaling.md) · [Logs, metrics & tracing](docs/26-observability.md) · [Security](docs/13-security.md) · [CLI](docs/14-cli.md) · [Upgrading to 0.5](docs/27-upgrading.md) |
306
398
  | 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) |
307
399
 
308
400
  The same docs are published at
@@ -330,17 +422,21 @@ Upgrade with `pip install -U pyweb-stack`.
330
422
 
331
423
  | Version | Highlights |
332
424
  |---|---|
425
+ | 0.5 | Models with migrations, forms, auth kit with passkeys, OAuth and an admin, row-level live updates over WebSockets on PyWeb's own server, durable jobs and schedules, `pyweb deploy` for docker, compose, k8s, fly, render and railway, logs, metrics, tracing and a dev toolbar, data-aware editor and MCP tools, the `saas` template, `pyweb upgrade` |
333
426
  | 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; 0.4.1: richer MCP tools; 0.4.2: VS Code run, new-app and MCP commands; 0.4.3: fixes, security hardening, gzip and caching; 0.4.4: key rotation, session revocation, typed arguments, Redis-shared limits, `check --production` |
334
427
  | 0.3 | Hydration, live updates (SSE), multi-file apps, language server + VS Code extension, browser playground, faster rendering, screenshot/test MCP tools |
335
428
  | 0.2 | MCP server for AI assistants, AI guide, project templates, `AGENTS.md`/`CLAUDE.md`, `llms.txt` |
336
429
  | 0.1 | First public release: compiler, reactive runtime, server rendering, typed RPC, sessions, databases, CLI |
337
430
 
338
- The test suite covers the parser, the Python→JavaScript translation
339
- (differentially, against CPython), the reactive runtime, server
340
- rendering, RPC, streaming, live queries, client-side navigation, every
341
- example app in Chromium, the playground on Pyodide, and the
342
- database/Redis layers against real Postgres, MySQL and Redis servers, on
343
- Python 3.10–3.13.
431
+ The test suite (about 1,200 tests) covers the parser, the
432
+ Python→JavaScript translation (differentially, against CPython), the
433
+ reactive runtime, server rendering, RPC, forms, Models and migrations
434
+ (with property tests), the HTTP and WebSocket parsers (fuzzed), live
435
+ patches, jobs on every backend, every example app in Chromium, the
436
+ playground on Pyodide, and the whole suite against real Postgres, MySQL
437
+ and Redis servers, on Python 3.10–3.13. CI also boots a
438
+ `pyweb deploy compose` stack and runs the VS Code extension in a real
439
+ VS Code.
344
440
 
345
441
  ## Contributing
346
442
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyweb-stack"
7
- version = "0.4.4"
7
+ version = "0.5.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" }
@@ -40,8 +40,11 @@ mysql = ["PyMySQL>=1.1"]
40
40
  redis = ["redis>=5"]
41
41
  crypto = ["cryptography>=42"]
42
42
  asgi = ["uvicorn>=0.30"]
43
- all = ["psycopg[binary]>=3.1", "PyMySQL>=1.1", "redis>=5", "cryptography>=42", "uvicorn>=0.30"]
44
- test = ["pytest>=8", "playwright>=1.45", "mcp>=1.2"]
43
+ auth = ["argon2-cffi>=23.1"]
44
+ otel = ["opentelemetry-sdk>=1.24", "opentelemetry-exporter-otlp-proto-http>=1.24"]
45
+ all = ["psycopg[binary]>=3.1", "PyMySQL>=1.1", "redis>=5", "cryptography>=42", "uvicorn>=0.30", "argon2-cffi>=23.1",
46
+ "opentelemetry-sdk>=1.24", "opentelemetry-exporter-otlp-proto-http>=1.24"]
47
+ test = ["pytest>=8", "playwright>=1.45", "mcp>=1.2", "hypothesis>=6.100", "pyyaml>=6", "opentelemetry-sdk>=1.24"]
45
48
 
46
49
  [project.urls]
47
50
  Homepage = "https://maanavkrishna.github.io/PyWeb/"
@@ -57,7 +60,7 @@ pyweb = "pyweb.cli:main"
57
60
  include = ["pyweb*"]
58
61
 
59
62
  [tool.setuptools.package-data]
60
- pyweb = ["runtime/browser/*.js", "py.typed", "ai/*.md", "templates/*.pyweb", "templates/*.css"]
63
+ pyweb = ["runtime/browser/*.js", "py.typed", "ai/*.md", "templates/*.pyweb", "templates/*.css", "templates/*/*.pyweb", "templates/*/*.py", "templates/*/*.md"]
61
64
 
62
65
  [tool.pytest.ini_options]
63
66
  testpaths = ["tests"]