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.
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/PKG-INFO +142 -35
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/README.md +130 -34
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyproject.toml +7 -4
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/__init__.py +21 -1
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/ai/guide.md +81 -17
- pyweb_stack-0.5.0/pyweb/app.py +136 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/app_loader.py +63 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/asgi.py +53 -1
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/auth.py +20 -0
- pyweb_stack-0.5.0/pyweb/authkit/__init__.py +425 -0
- pyweb_stack-0.5.0/pyweb/authkit/admin.py +247 -0
- pyweb_stack-0.5.0/pyweb/authkit/html.py +107 -0
- pyweb_stack-0.5.0/pyweb/authkit/models.py +134 -0
- pyweb_stack-0.5.0/pyweb/authkit/oauth.py +107 -0
- pyweb_stack-0.5.0/pyweb/authkit/pages.py +765 -0
- pyweb_stack-0.5.0/pyweb/authkit/passwords.py +158 -0
- pyweb_stack-0.5.0/pyweb/authkit/throttle.py +88 -0
- pyweb_stack-0.5.0/pyweb/authkit/webauthn.py +210 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/build.py +20 -11
- pyweb_stack-0.5.0/pyweb/cli/__init__.py +861 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/lower.py +247 -29
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/pipeline.py +1 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/config.py +33 -3
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/context.py +1 -1
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/db/__init__.py +365 -37
- pyweb_stack-0.5.0/pyweb/db/dialect.py +305 -0
- pyweb_stack-0.5.0/pyweb/db/migrate.py +567 -0
- pyweb_stack-0.5.0/pyweb/db/schema.py +884 -0
- pyweb_stack-0.5.0/pyweb/db/seeds.py +56 -0
- pyweb_stack-0.5.0/pyweb/decorators.py +108 -0
- pyweb_stack-0.5.0/pyweb/deploy/__init__.py +16 -0
- pyweb_stack-0.5.0/pyweb/deploy/plan.py +285 -0
- pyweb_stack-0.5.0/pyweb/deploy/targets.py +498 -0
- pyweb_stack-0.5.0/pyweb/deprecation.py +34 -0
- pyweb_stack-0.5.0/pyweb/forms.py +828 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/hosting.py +58 -6
- pyweb_stack-0.5.0/pyweb/jobs/__init__.py +30 -0
- pyweb_stack-0.5.0/pyweb/jobs/builtin.py +51 -0
- pyweb_stack-0.5.0/pyweb/jobs/core.py +265 -0
- pyweb_stack-0.5.0/pyweb/jobs/db.py +286 -0
- pyweb_stack-0.4.4/pyweb/jobs.py → pyweb_stack-0.5.0/pyweb/jobs/legacy.py +10 -3
- pyweb_stack-0.5.0/pyweb/jobs/memory.py +215 -0
- pyweb_stack-0.5.0/pyweb/jobs/redis.py +264 -0
- pyweb_stack-0.5.0/pyweb/jobs/schedule.py +179 -0
- pyweb_stack-0.5.0/pyweb/jobs/worker.py +407 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/keys.py +4 -5
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/livedata.py +100 -15
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/lsp.py +135 -8
- pyweb_stack-0.5.0/pyweb/lsp_data.py +326 -0
- pyweb_stack-0.5.0/pyweb/mail.py +184 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/mcp.py +263 -7
- pyweb_stack-0.5.0/pyweb/models/__init__.py +1067 -0
- pyweb_stack-0.5.0/pyweb/models/fields.py +479 -0
- pyweb_stack-0.5.0/pyweb/models/query.py +663 -0
- pyweb_stack-0.5.0/pyweb/models/relations.py +670 -0
- pyweb_stack-0.5.0/pyweb/net/__init__.py +1 -0
- pyweb_stack-0.5.0/pyweb/net/http.py +233 -0
- pyweb_stack-0.5.0/pyweb/net/live.py +441 -0
- pyweb_stack-0.5.0/pyweb/net/server.py +557 -0
- pyweb_stack-0.5.0/pyweb/net/ws.py +253 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/packages.py +1 -1
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/realtime.py +31 -5
- pyweb_stack-0.5.0/pyweb/rooms.py +98 -0
- pyweb_stack-0.5.0/pyweb/rules.py +113 -0
- pyweb_stack-0.5.0/pyweb/runtime/browser/auth.js +97 -0
- pyweb_stack-0.5.0/pyweb/runtime/browser/forms.js +252 -0
- pyweb_stack-0.5.0/pyweb/runtime/browser/live.js +167 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/runtime/browser/runtime.js +0 -53
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/runtime/server/__init__.py +322 -13
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/runtime/server/argcheck.py +47 -2
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/security.py +70 -3
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/serve.py +34 -12
- pyweb_stack-0.5.0/pyweb/storage.py +260 -0
- pyweb_stack-0.5.0/pyweb/telemetry/__init__.py +200 -0
- pyweb_stack-0.5.0/pyweb/telemetry/devtools.py +187 -0
- pyweb_stack-0.5.0/pyweb/telemetry/instruments.py +74 -0
- pyweb_stack-0.5.0/pyweb/telemetry/logs.py +139 -0
- pyweb_stack-0.5.0/pyweb/telemetry/metrics.py +285 -0
- pyweb_stack-0.5.0/pyweb/telemetry/tracing.py +270 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/blog.pyweb +11 -20
- pyweb_stack-0.5.0/pyweb/templates/saas/app.pyweb +168 -0
- pyweb_stack-0.5.0/pyweb/templates/saas/seeds.py +14 -0
- pyweb_stack-0.5.0/pyweb/templates/saas/test_app.py +67 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/testing.py +90 -28
- pyweb_stack-0.5.0/pyweb/upgrade.py +187 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb_stack.egg-info/PKG-INFO +142 -35
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb_stack.egg-info/SOURCES.txt +67 -3
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb_stack.egg-info/requires.txt +13 -0
- pyweb_stack-0.5.0/tests/test_authkit.py +650 -0
- pyweb_stack-0.5.0/tests/test_authkit_units.py +286 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_cli_deploy.py +19 -12
- pyweb_stack-0.5.0/tests/test_cron.py +199 -0
- pyweb_stack-0.5.0/tests/test_data_layer.py +702 -0
- pyweb_stack-0.5.0/tests/test_deploy_v5.py +305 -0
- pyweb_stack-0.5.0/tests/test_dx_v5.py +504 -0
- pyweb_stack-0.5.0/tests/test_forms_v5.py +448 -0
- pyweb_stack-0.5.0/tests/test_jobs_app.py +294 -0
- pyweb_stack-0.5.0/tests/test_jobs_v5.py +387 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_keys_sessions.py +3 -2
- pyweb_stack-0.5.0/tests/test_live_patches.py +93 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_lsp.py +8 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_mcp.py +7 -1
- pyweb_stack-0.5.0/tests/test_migrations_property.py +98 -0
- pyweb_stack-0.5.0/tests/test_migrations_v5.py +392 -0
- pyweb_stack-0.5.0/tests/test_net.py +637 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_production_gaps.py +2 -1
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_realtime_transport.py +5 -3
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_rpc_production.py +3 -2
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_server_db.py +6 -1
- pyweb_stack-0.5.0/tests/test_telemetry.py +536 -0
- pyweb_stack-0.4.4/pyweb/app.py +0 -78
- pyweb_stack-0.4.4/pyweb/cli/__init__.py +0 -553
- pyweb_stack-0.4.4/pyweb/db/migrate.py +0 -143
- pyweb_stack-0.4.4/pyweb/decorators.py +0 -49
- pyweb_stack-0.4.4/pyweb/forms.py +0 -108
- pyweb_stack-0.4.4/pyweb/models.py +0 -268
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/LICENSE +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/bench.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/browser.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/cache.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/cli/__main__.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/__init__.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/ast.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/codegen/__init__.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/codegen/ir.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/errors.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/parser.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/pyjs.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/compiler/rpc.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/css.py +0 -0
- /pyweb_stack-0.4.4/pyweb/deploy.py → /pyweb_stack-0.5.0/pyweb/deploy/legacy.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/dts.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/livetable.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/markdown.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/observability.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/platform.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/plugins.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/py.typed +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/rpc.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/runtime/browser/markdown.js +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/ssr.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/sync.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/ai-chat.pyweb +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/app.css +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/auth.pyweb +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/chat.pyweb +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/counter.pyweb +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/templates/todo.pyweb +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb/uploads.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb_stack.egg-info/dependency_links.txt +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb_stack.egg-info/entry_points.txt +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/pyweb_stack.egg-info/top_level.txt +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/setup.cfg +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_auth.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_auth_contract.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_auth_security.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_auth_v1.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_backplane.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_browser_build.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_budget.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_codegen.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_compiler.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_db_production.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_deploy_upload_render.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_docs.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_dts.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_e2e.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_examples.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_forms_security_obs.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_jobs_cache_realtime.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_livedata.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_markdown.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_models_db.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_multifile.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_multipage.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_observability.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_packages.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_parser.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_parser_v1.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_plugins_platform_bench.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_pyjs_semantics.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_reactivity.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_reference_apps.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_request_hardening.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_rpc_placement.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_runtime_signals.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_static_hashing.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_static_serving.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_streaming.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_sync_live.py +0 -0
- {pyweb_stack-0.4.4 → pyweb_stack-0.5.0}/tests/test_systems.py +0 -0
- {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.
|
|
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,
|
|
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
|
[](https://github.com/MaanavKrishna/PyWeb/actions/workflows/ci.yml)
|
|
65
77
|
[](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
|
-
###
|
|
205
|
+
### Data, with migrations that write themselves
|
|
137
206
|
|
|
138
207
|
```pyweb
|
|
139
|
-
from pyweb import App,
|
|
140
|
-
from pyweb.db import connect
|
|
208
|
+
from pyweb import App, Field, Model
|
|
141
209
|
|
|
142
|
-
app = App()
|
|
143
|
-
|
|
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 =
|
|
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
|
-
|
|
157
|
-
|
|
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
|
-
~
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
|
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
|
|
284
|
-
pyweb
|
|
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
|
-
|
|
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,
|
|
313
|
-
|
|
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
|
|
333
|
-
|
|
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) |
|
|
345
|
-
| [`auth`](examples/auth/app.pyweb) | registration, password hashing
|
|
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) · [
|
|
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
|
|
393
|
-
(differentially, against CPython), the
|
|
394
|
-
rendering, RPC,
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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,
|
|
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
|
[](https://github.com/MaanavKrishna/PyWeb/actions/workflows/ci.yml)
|
|
11
12
|
[](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
|
-
###
|
|
140
|
+
### Data, with migrations that write themselves
|
|
83
141
|
|
|
84
142
|
```pyweb
|
|
85
|
-
from pyweb import App,
|
|
86
|
-
from pyweb.db import connect
|
|
143
|
+
from pyweb import App, Field, Model
|
|
87
144
|
|
|
88
|
-
app = App()
|
|
89
|
-
|
|
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 =
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
~
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
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
|
|
230
|
-
pyweb
|
|
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
|
-
|
|
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,
|
|
259
|
-
|
|
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
|
|
279
|
-
|
|
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) |
|
|
291
|
-
| [`auth`](examples/auth/app.pyweb) | registration, password hashing
|
|
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) · [
|
|
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
|
|
339
|
-
(differentially, against CPython), the
|
|
340
|
-
rendering, RPC,
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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.
|
|
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
|
-
|
|
44
|
-
|
|
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"]
|