pyweb-stack 0.5.0__tar.gz → 0.6.1__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.6.1/CHANGELOG.md +1135 -0
- pyweb_stack-0.6.1/MANIFEST.in +3 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/PKG-INFO +117 -32
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/README.md +105 -29
- pyweb_stack-0.6.1/THREAT_MODEL.md +201 -0
- pyweb_stack-0.6.1/docs/01-introduction.md +166 -0
- pyweb_stack-0.6.1/docs/02-quickstart.md +117 -0
- pyweb_stack-0.6.1/docs/03-tutorial.md +300 -0
- pyweb_stack-0.6.1/docs/04-pyweb-files.md +345 -0
- pyweb_stack-0.6.1/docs/05-reactivity.md +149 -0
- pyweb_stack-0.6.1/docs/06-server-functions.md +382 -0
- pyweb_stack-0.6.1/docs/07-browser-python.md +98 -0
- pyweb_stack-0.6.1/docs/08-pages-routing-assets.md +202 -0
- pyweb_stack-0.6.1/docs/09-data.md +432 -0
- pyweb_stack-0.6.1/docs/10-auth.md +324 -0
- pyweb_stack-0.6.1/docs/11-testing.md +193 -0
- pyweb_stack-0.6.1/docs/12-deployment.md +267 -0
- pyweb_stack-0.6.1/docs/13-security.md +282 -0
- pyweb_stack-0.6.1/docs/14-cli.md +183 -0
- pyweb_stack-0.6.1/docs/15-toolkit.md +110 -0
- pyweb_stack-0.6.1/docs/16-limitations-roadmap.md +74 -0
- pyweb_stack-0.6.1/docs/17-ai-assistants.md +224 -0
- pyweb_stack-0.6.1/docs/18-npm-packages.md +210 -0
- pyweb_stack-0.6.1/docs/19-layouts-navigation.md +148 -0
- pyweb_stack-0.6.1/docs/20-ai-apps.md +146 -0
- pyweb_stack-0.6.1/docs/21-live-data.md +130 -0
- pyweb_stack-0.6.1/docs/22-recipe-web3.md +146 -0
- pyweb_stack-0.6.1/docs/23-forms.md +192 -0
- pyweb_stack-0.6.1/docs/24-jobs.md +186 -0
- pyweb_stack-0.6.1/docs/25-scaling.md +89 -0
- pyweb_stack-0.6.1/docs/26-observability.md +207 -0
- pyweb_stack-0.6.1/docs/27-upgrading.md +206 -0
- pyweb_stack-0.6.1/docs/28-saas-tutorial.md +234 -0
- pyweb_stack-0.6.1/docs/29-performance.md +163 -0
- pyweb_stack-0.6.1/docs/30-teams.md +199 -0
- pyweb_stack-0.6.1/docs/31-pwa-push.md +168 -0
- pyweb_stack-0.6.1/docs/32-desktop.md +190 -0
- pyweb_stack-0.6.1/docs/33-webhooks-files.md +103 -0
- pyweb_stack-0.6.1/docs/34-ui-and-motion.md +537 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyproject.toml +12 -6
- pyweb_stack-0.6.1/pyweb/__init__.py +149 -0
- pyweb_stack-0.6.1/pyweb/_speedups.py +89 -0
- pyweb_stack-0.6.1/pyweb/_templates.py +3 -0
- pyweb_stack-0.6.1/pyweb/a11y.py +121 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/ai/guide.md +134 -11
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/app.py +37 -6
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/app_loader.py +137 -10
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/asgi.py +6 -4
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/auth.py +6 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/authkit/__init__.py +130 -4
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/authkit/admin.py +25 -10
- pyweb_stack-0.6.1/pyweb/authkit/html.py +141 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/authkit/models.py +72 -1
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/authkit/oauth.py +3 -0
- pyweb_stack-0.6.1/pyweb/authkit/orgpages.py +234 -0
- pyweb_stack-0.6.1/pyweb/authkit/orgs.py +98 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/authkit/pages.py +138 -11
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/authkit/passwords.py +3 -0
- pyweb_stack-0.6.1/pyweb/authkit/privacy.py +101 -0
- pyweb_stack-0.6.1/pyweb/authkit/teams.py +286 -0
- pyweb_stack-0.6.1/pyweb/authkit/tokens.py +81 -0
- pyweb_stack-0.6.1/pyweb/bench.py +373 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/build.py +13 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/cli/__init__.py +274 -39
- pyweb_stack-0.6.1/pyweb/compiler/errors.py +223 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/compiler/lower.py +158 -23
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/compiler/parser.py +40 -4
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/compiler/pipeline.py +97 -10
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/compiler/pyjs.py +84 -9
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/config.py +31 -2
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/context.py +29 -0
- pyweb_stack-0.6.1/pyweb/csp_report.py +116 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/db/__init__.py +39 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/db/migrate.py +2 -0
- pyweb_stack-0.6.1/pyweb/db/rls.py +120 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/db/schema.py +6 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/decorators.py +29 -3
- pyweb_stack-0.6.1/pyweb/desktop/__init__.py +243 -0
- pyweb_stack-0.6.1/pyweb/desktop/gate.py +164 -0
- pyweb_stack-0.6.1/pyweb/desktop/package.py +255 -0
- pyweb_stack-0.6.1/pyweb/desktop/shell.py +195 -0
- pyweb_stack-0.6.1/pyweb/devlink.py +236 -0
- pyweb_stack-0.6.1/pyweb/doctor.py +303 -0
- pyweb_stack-0.6.1/pyweb/fieldcrypt.py +186 -0
- pyweb_stack-0.6.1/pyweb/filegc.py +116 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/forms.py +26 -5
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/hosting.py +83 -8
- pyweb_stack-0.6.1/pyweb/http.py +265 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/jobs/core.py +2 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/jobs/memory.py +2 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/jobs/worker.py +5 -1
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/livedata.py +4 -3
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/lsp.py +13 -0
- pyweb_stack-0.6.1/pyweb/lsp_teams.py +139 -0
- pyweb_stack-0.6.1/pyweb/lsp_ui.py +175 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/mail.py +3 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/mcp.py +707 -180
- pyweb_stack-0.6.1/pyweb/mcp_http.py +219 -0
- pyweb_stack-0.6.1/pyweb/mcp_tools.py +708 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/models/__init__.py +104 -11
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/models/fields.py +50 -1
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/models/query.py +43 -0
- pyweb_stack-0.6.1/pyweb/motion.py +282 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/net/live.py +2 -1
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/net/server.py +6 -3
- pyweb_stack-0.6.1/pyweb/openapi.py +228 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/packages.py +3 -0
- pyweb_stack-0.6.1/pyweb/platform.py +100 -0
- pyweb_stack-0.6.1/pyweb/profile.py +123 -0
- pyweb_stack-0.6.1/pyweb/push.py +468 -0
- pyweb_stack-0.6.1/pyweb/pwa.py +423 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/desktop.js +41 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/dev/reload.js +102 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/markdown.js +8 -1
- pyweb_stack-0.6.1/pyweb/runtime/browser/motion-api.js +68 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/motion-count.js +48 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/motion-drag.js +105 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/motion-layout.js +71 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/motion-math.js +132 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/motion.js +498 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/pwa.js +120 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/runtime.js +120 -21
- pyweb_stack-0.6.1/pyweb/runtime/browser/sw.js +149 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/accordion.js +89 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/calendar.js +162 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/carousel.js +105 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/checkbox.js +9 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/collapsible.js +12 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/combobox.js +91 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/command.js +72 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/context-menu.js +36 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/core.js +286 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/dialog.js +89 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/hover-card.js +39 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/index.js +86 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/listbox.js +74 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/menu.js +168 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/menubar.js +58 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/navigation-menu.js +94 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/otp.js +29 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/popover.js +75 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/radio-group.js +20 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/resizable.js +101 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/select.js +104 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/sidebar.js +71 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/slider.js +19 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/tabs.js +74 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/toaster.js +158 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/toggle-group.js +45 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/toggle.js +15 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/ui/tooltip.js +61 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/runtime/server/__init__.py +127 -17
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/runtime/server/argcheck.py +30 -0
- pyweb_stack-0.6.1/pyweb/sbom.py +147 -0
- pyweb_stack-0.6.1/pyweb/secret_scan.py +102 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/security.py +3 -1
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/serve.py +18 -1
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/ssr.py +173 -6
- pyweb_stack-0.6.1/pyweb/ssr_codegen.py +263 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/storage.py +13 -1
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/telemetry/devtools.py +168 -1
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/telemetry/instruments.py +2 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/telemetry/logs.py +31 -9
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/telemetry/metrics.py +5 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/telemetry/tracing.py +16 -6
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/templates/ai-chat.pyweb +9 -7
- pyweb_stack-0.6.1/pyweb/templates/app.css +105 -0
- pyweb_stack-0.6.1/pyweb/templates/auth.pyweb +135 -0
- pyweb_stack-0.6.1/pyweb/templates/blog.pyweb +87 -0
- pyweb_stack-0.6.1/pyweb/templates/chat.pyweb +100 -0
- pyweb_stack-0.6.1/pyweb/templates/counter.pyweb +37 -0
- pyweb_stack-0.6.1/pyweb/templates/desktop/app.pyweb +125 -0
- pyweb_stack-0.6.1/pyweb/templates/desktop/test_app.py +58 -0
- pyweb_stack-0.6.1/pyweb/templates/saas/app.pyweb +269 -0
- pyweb_stack-0.6.1/pyweb/templates/saas/seeds.py +19 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/templates/saas/test_app.py +28 -4
- pyweb_stack-0.6.1/pyweb/templates/todo.pyweb +84 -0
- pyweb_stack-0.6.1/pyweb/tenancy.py +123 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/testing.py +59 -6
- pyweb_stack-0.6.1/pyweb/ui/__init__.py +96 -0
- pyweb_stack-0.6.1/pyweb/ui/cli.py +86 -0
- pyweb_stack-0.6.1/pyweb/ui/extending.py +147 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/button.pyweb +25 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/card.pyweb +25 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/carousel.pyweb +41 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/chart.pyweb +292 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/command.pyweb +113 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/controls.pyweb +131 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/data.pyweb +202 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/date.pyweb +35 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/dialog.pyweb +137 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/disclosure.pyweb +85 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/display.pyweb +59 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/layout.pyweb +264 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/menu.pyweb +130 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/overlay.pyweb +65 -0
- pyweb_stack-0.6.1/pyweb/ui/kit/style.pyweb +118 -0
- pyweb_stack-0.6.1/pyweb/ui/serve.py +96 -0
- pyweb_stack-0.6.1/pyweb/ui/source_edit.py +111 -0
- pyweb_stack-0.6.1/pyweb/ui/static/ui.css +617 -0
- pyweb_stack-0.6.1/pyweb/ui/theme.py +293 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/upgrade.py +70 -0
- pyweb_stack-0.6.1/pyweb/webhooks.py +334 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/PKG-INFO +117 -32
- pyweb_stack-0.6.1/pyweb_stack.egg-info/SOURCES.txt +351 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/requires.txt +10 -0
- pyweb_stack-0.6.1/setup.py +24 -0
- pyweb_stack-0.6.1/tests/test_account_privacy.py +79 -0
- pyweb_stack-0.6.1/tests/test_api_tokens.py +107 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_auth.py +2 -1
- pyweb_stack-0.6.1/tests/test_auth_sessions.py +99 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_authkit.py +28 -1
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_budget.py +1 -1
- pyweb_stack-0.6.1/tests/test_cli_test.py +44 -0
- pyweb_stack-0.6.1/tests/test_compile_cache.py +88 -0
- pyweb_stack-0.6.1/tests/test_customise.py +286 -0
- pyweb_stack-0.6.1/tests/test_desktop.py +467 -0
- pyweb_stack-0.6.1/tests/test_dev_reload.py +53 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_docs.py +4 -2
- pyweb_stack-0.6.1/tests/test_doctor.py +128 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_dx_v5.py +3 -1
- pyweb_stack-0.6.1/tests/test_encrypted_fields.py +151 -0
- pyweb_stack-0.6.1/tests/test_error_messages.py +164 -0
- pyweb_stack-0.6.1/tests/test_filegc.py +65 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_forms_v5.py +2 -1
- pyweb_stack-0.6.1/tests/test_fuzz_requests.py +143 -0
- pyweb_stack-0.6.1/tests/test_http_fetch.py +136 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_jobs_app.py +5 -1
- pyweb_stack-0.6.1/tests/test_lsp_teams.py +67 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_mcp.py +24 -6
- pyweb_stack-0.6.1/tests/test_mcp_teams.py +54 -0
- pyweb_stack-0.6.1/tests/test_mcp_v6.py +401 -0
- pyweb_stack-0.6.1/tests/test_motion.py +268 -0
- pyweb_stack-0.6.1/tests/test_openapi.py +107 -0
- pyweb_stack-0.6.1/tests/test_org_pages.py +150 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_plugins_platform_bench.py +21 -0
- pyweb_stack-0.6.1/tests/test_profile.py +63 -0
- pyweb_stack-0.6.1/tests/test_push.py +284 -0
- pyweb_stack-0.6.1/tests/test_pwa.py +199 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_pyjs_semantics.py +20 -2
- pyweb_stack-0.6.1/tests/test_rate_per_function.py +80 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_reference_apps.py +2 -1
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_request_hardening.py +26 -0
- pyweb_stack-0.6.1/tests/test_sbom.py +62 -0
- pyweb_stack-0.6.1/tests/test_secret_scan.py +52 -0
- pyweb_stack-0.6.1/tests/test_speedups.py +57 -0
- pyweb_stack-0.6.1/tests/test_ssr_codegen.py +123 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_systems.py +80 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_telemetry.py +40 -0
- pyweb_stack-0.6.1/tests/test_tenancy.py +321 -0
- pyweb_stack-0.6.1/tests/test_testing_orgs.py +116 -0
- pyweb_stack-0.6.1/tests/test_threat_model.py +20 -0
- pyweb_stack-0.6.1/tests/test_trusted_types.py +95 -0
- pyweb_stack-0.6.1/tests/test_ui_kit.py +469 -0
- pyweb_stack-0.6.1/tests/test_webhooks.py +161 -0
- pyweb_stack-0.5.0/pyweb/__init__.py +0 -102
- pyweb_stack-0.5.0/pyweb/authkit/html.py +0 -107
- pyweb_stack-0.5.0/pyweb/bench.py +0 -125
- pyweb_stack-0.5.0/pyweb/compiler/errors.py +0 -29
- pyweb_stack-0.5.0/pyweb/platform.py +0 -69
- pyweb_stack-0.5.0/pyweb/templates/app.css +0 -50
- pyweb_stack-0.5.0/pyweb/templates/auth.pyweb +0 -111
- pyweb_stack-0.5.0/pyweb/templates/blog.pyweb +0 -75
- pyweb_stack-0.5.0/pyweb/templates/chat.pyweb +0 -81
- pyweb_stack-0.5.0/pyweb/templates/counter.pyweb +0 -26
- pyweb_stack-0.5.0/pyweb/templates/saas/app.pyweb +0 -168
- pyweb_stack-0.5.0/pyweb/templates/saas/seeds.py +0 -14
- pyweb_stack-0.5.0/pyweb/templates/todo.pyweb +0 -72
- pyweb_stack-0.5.0/pyweb_stack.egg-info/SOURCES.txt +0 -184
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/LICENSE +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/authkit/throttle.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/authkit/webauthn.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/browser.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/cache.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/cli/__main__.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/compiler/__init__.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/compiler/ast.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/compiler/codegen/__init__.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/compiler/codegen/ir.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/compiler/rpc.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/css.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/db/dialect.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/db/seeds.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/deploy/__init__.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/deploy/legacy.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/deploy/plan.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/deploy/targets.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/deprecation.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/dts.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/jobs/__init__.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/jobs/builtin.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/jobs/db.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/jobs/legacy.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/jobs/redis.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/jobs/schedule.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/keys.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/livetable.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/lsp_data.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/markdown.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/models/relations.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/net/__init__.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/net/http.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/net/ws.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/observability.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/plugins.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/py.typed +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/realtime.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/rooms.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/rpc.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/rules.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/auth.js +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/forms.js +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/live.js +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/sync.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/telemetry/__init__.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb/uploads.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/dependency_links.txt +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/entry_points.txt +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/top_level.txt +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/setup.cfg +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_auth_contract.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_auth_security.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_auth_v1.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_authkit_units.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_backplane.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_browser_build.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_cli_deploy.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_codegen.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_compiler.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_cron.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_data_layer.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_db_production.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_deploy_upload_render.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_deploy_v5.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_dts.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_e2e.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_examples.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_forms_security_obs.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_jobs_cache_realtime.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_jobs_v5.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_keys_sessions.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_live_patches.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_livedata.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_lsp.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_markdown.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_migrations_property.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_migrations_v5.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_models_db.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_multifile.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_multipage.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_net.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_observability.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_packages.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_parser.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_parser_v1.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_production_gaps.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_reactivity.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_realtime_transport.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_rpc_placement.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_rpc_production.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_runtime_signals.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_server_db.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_static_hashing.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_static_serving.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_streaming.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_sync_live.py +0 -0
- {pyweb_stack-0.5.0 → pyweb_stack-0.6.1}/tests/test_website.py +0 -0
|
@@ -0,0 +1,1135 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format
|
|
4
|
+
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
|
|
5
|
+
project uses [semantic versioning](https://semver.org).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.6.1]
|
|
10
|
+
|
|
11
|
+
Better error messages, `pyweb doctor`, a `pyweb dev` that keeps your place
|
|
12
|
+
when it reloads, much faster reloads and tests, and fixes found by
|
|
13
|
+
installing 0.6.0 from PyPI and putting every template through every
|
|
14
|
+
command. Nothing to change in your apps.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **Better error messages.** Compile errors show the line with a caret under
|
|
18
|
+
the problem, suggest the name you meant (a prop, component, variable,
|
|
19
|
+
server function argument, kit variant or motion preset: "did you mean
|
|
20
|
+
'name'?"), and link the docs page that explains them. The CLI, the
|
|
21
|
+
`pyweb dev` overlay and the MCP tools (`frame`, `docs` fields) show the
|
|
22
|
+
same. When page code raises in `pyweb dev`, the error page starts with the
|
|
23
|
+
frames in your own files and their source lines.
|
|
24
|
+
- **`pyweb dev` keeps your place when it reloads.** After a save, the page
|
|
25
|
+
comes back with its variables (a counter's count, a bound input's text),
|
|
26
|
+
unbound form fields, scroll position and focus as they were. A variable
|
|
27
|
+
whose type changed, or that was renamed, starts over; add `?fresh` to the
|
|
28
|
+
URL to start everything over. Production builds only grow by the names
|
|
29
|
+
of page variables.
|
|
30
|
+
- **`pyweb doctor`** checks the project and the machine in one go: Python,
|
|
31
|
+
whether the app compiles, optional packages the app needs, the database
|
|
32
|
+
and pending migrations, Redis, secrets written in the source, changes
|
|
33
|
+
`pyweb upgrade` wants, a stale dev server link, tests, the port and the
|
|
34
|
+
editor extension, each with a fix (`--production` adds the production
|
|
35
|
+
settings, `--json` for CI). Assistants get it as the `pyweb_doctor` tool.
|
|
36
|
+
- **Faster reloads and tests.** The UI kit is compiled once per process and
|
|
37
|
+
each file's Python code is reused until it changes: on the saas example a
|
|
38
|
+
`pyweb dev` reload after a save went from about 255 to 60 ms, a
|
|
39
|
+
`TestClient` from about 255 to 55 ms, and compiling from about 240 to
|
|
40
|
+
60 ms. The dev server notices a save within 0.1 s (was 0.4 s) and the
|
|
41
|
+
page asks for the new version every 0.25 s (was 0.6 s).
|
|
42
|
+
`python -m pyweb.bench --system` reports `reload_ms` and `test_client_ms`,
|
|
43
|
+
and CI's benchmark gate watches them.
|
|
44
|
+
- `pyweb check` warns about kit props and motion values that won't work
|
|
45
|
+
(`<Button variant="outlin">`), as the editor already did.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
- `pyweb check` printed the file and line twice for compile errors.
|
|
49
|
+
- Components drawn as a kit button (`DialogClose`) were flagged by the
|
|
50
|
+
editor for `variant="default"`, which they support.
|
|
51
|
+
- `pyweb test` with no path ran `pytest tests/`, which new apps don't have
|
|
52
|
+
(their test is `test_app.py` next to `app.pyweb`), so it failed in every
|
|
53
|
+
new project. It now runs the current folder, and accepts the app's
|
|
54
|
+
`.pyweb` file as well as a test file or folder.
|
|
55
|
+
- The `pyweb_test` MCP tool given the app's `.pyweb` file (as every other
|
|
56
|
+
tool takes) found no tests; it now tests that file's folder.
|
|
57
|
+
- `pyweb upgrade` says to apply pending migrations before creating the
|
|
58
|
+
`auth_sessions` one (on a fresh checkout `pyweb db diff` refused to run),
|
|
59
|
+
and notes that 0.6's `pyweb check` reports accessibility problems, which
|
|
60
|
+
`--strict` treats as failures.
|
|
61
|
+
|
|
62
|
+
## [0.6.0]
|
|
63
|
+
|
|
64
|
+
Teams, installable and desktop apps, a UI kit and motion, and security by
|
|
65
|
+
design: organizations with roles, invitations and tenant-scoped Models
|
|
66
|
+
(enforced by the database on Postgres), API tokens and OpenAPI, webhooks,
|
|
67
|
+
PWA and Web Push, native desktop windows and installers, a styled and
|
|
68
|
+
accessible component kit with springs and layout animations, Trusted
|
|
69
|
+
Types, encrypted fields and a threat model, about 2.5× faster serving,
|
|
70
|
+
Python 3.15 and free-threaded builds, and a much more capable MCP server.
|
|
71
|
+
Most 0.5 apps run unchanged; see [Upgrading to 0.6](docs/27-upgrading.md)
|
|
72
|
+
and run `pyweb upgrade --check`.
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
- **Python 3.14, 3.15 and free-threaded 3.15t** are supported and tested in
|
|
76
|
+
CI. On a free-threaded build one `pyweb serve` process uses every core
|
|
77
|
+
(about 3× the requests per second of the same build with the GIL).
|
|
78
|
+
- **Python 3.15 features.** Browser code can use unpacking in
|
|
79
|
+
comprehensions (`[*row for row in rows]`, `{**d for d in ds}`). `lazy
|
|
80
|
+
import` is server-only Python, and older Pythons get a clear error that
|
|
81
|
+
names the version it needs.
|
|
82
|
+
- **`pyweb profile app.pyweb`** serves the app and records where the time
|
|
83
|
+
goes while you use it: a Tachyon flame graph on Python 3.15, a text
|
|
84
|
+
report before.
|
|
85
|
+
- **`pyweb-stack[speed]`** adds orjson for live queries, WebSocket messages,
|
|
86
|
+
page state and server function results (a 10,000-row live query encodes
|
|
87
|
+
10× faster).
|
|
88
|
+
- **`python -m pyweb.bench --system`** measures requests per second over
|
|
89
|
+
sockets, a signed-in page, start-up and live queries; `--compare DIR`
|
|
90
|
+
fails on a 15% regression against another checkout. CI runs it on
|
|
91
|
+
every push.
|
|
92
|
+
- **`PYWEB_SSR_VERIFY=1`** renders every page with both renderers and
|
|
93
|
+
fails if they differ.
|
|
94
|
+
- New docs page: [Performance](docs/29-performance.md), including why
|
|
95
|
+
PyWeb has no Rust core.
|
|
96
|
+
- **Trusted Types.** Pages tell the browser to refuse plain strings in
|
|
97
|
+
HTML and script sinks; PyWeb's runtime uses private policies
|
|
98
|
+
(Markdown, client navigation, the service worker). Pages that use npm packages are exempt.
|
|
99
|
+
- **Encrypted fields.** `Field(encrypted=True)` stores AES-256-GCM
|
|
100
|
+
ciphertext under `PYWEB_DATA_KEY` (kept apart from the session secret);
|
|
101
|
+
`searchable=True` adds a blind index for exact lookups. Rotate with
|
|
102
|
+
`PYWEB_DATA_KEY_PREVIOUS` and `pyweb db reencrypt`.
|
|
103
|
+
- **Secret scanning.** `pyweb check` finds keys written into the app and
|
|
104
|
+
the files next to it: known formats fail the check, random-looking values
|
|
105
|
+
in secret-named variables are warnings.
|
|
106
|
+
- **`pyweb.http.fetch`** for URLs other people choose: refuses internal
|
|
107
|
+
addresses (including through redirects and DNS rebinding), with time and
|
|
108
|
+
size limits. `PYWEB_FETCH_ALLOW` lets it reach internal services you name.
|
|
109
|
+
- **`@server(rate="5/minute", per="user"|"ip"|"all")`** limits one server
|
|
110
|
+
function (429 with `Retry-After`; shared through Redis when configured).
|
|
111
|
+
- **Webhooks.** `@app.webhook("/hooks/stripe", secret=..., scheme="stripe")`
|
|
112
|
+
checks GitHub, Stripe and Standard Webhooks signatures, drops repeats
|
|
113
|
+
and replays, and runs the handler as a retried job. `webhooks.send()` /
|
|
114
|
+
`publish()` sign deliveries (Standard Webhooks), retry them over about a
|
|
115
|
+
day through the SSRF-safe fetch, and log each attempt; `WebhookEndpoint`
|
|
116
|
+
rows hold each organization's endpoints.
|
|
117
|
+
- **Installable apps.** `App(pwa=True)` (or `pwa=PWA(name=, icon=,
|
|
118
|
+
theme_color=, offline_page=, cache_pages=...)`) serves a web app
|
|
119
|
+
manifest, icons made from one PNG (or drawn for you) and a service
|
|
120
|
+
worker: static files work offline, pages without a connection show an
|
|
121
|
+
offline page, and with `cache_pages=True` pages visitors opened while
|
|
122
|
+
signed out stay readable offline. The worker is registered through a
|
|
123
|
+
third Trusted Types policy, `pyweb-sw`, which accepts only `/sw.js`.
|
|
124
|
+
- **A UI kit** (`from pyweb.ui import Button, Card, Badge, Alert, ...`):
|
|
125
|
+
themed, accessible, server-rendered components. `App(theme=Theme(accent=,
|
|
126
|
+
radius=, font=, density=, dark=))` or a preset computes 12-step OKLCH
|
|
127
|
+
colour scales (accent, gray, status colours; light and dark) with
|
|
128
|
+
contrast checked to 4.5:1. The kit's CSS sits in a cascade layer so
|
|
129
|
+
app CSS always wins, and every part carries `data-part`/`data-state`
|
|
130
|
+
attributes. `pyweb ui add|diff|list` copies a component's source into
|
|
131
|
+
the app. Overlays and pickers follow the WAI-ARIA Authoring Practices:
|
|
132
|
+
Dialog, AlertDialog, Sheet, Popover, Tooltip, HoverCard, DropdownMenu,
|
|
133
|
+
ContextMenu, Menubar (with checkbox/radio items and submenus),
|
|
134
|
+
SelectMenu, Combobox, a Command palette (⌘K) and sonner-style toasts
|
|
135
|
+
(`pyweb.ui.toast()` from server code). Also Tabs, Accordion and
|
|
136
|
+
Collapsible (native `<details>`), Checkbox, Switch, RadioGroup, Toggle,
|
|
137
|
+
ToggleGroup, Slider, InputOTP, a Sidebar layout, NavigationMenu,
|
|
138
|
+
Breadcrumb, Pagination, Resizable panels, ScrollArea, Calendar and
|
|
139
|
+
DatePicker, Carousel, DataTable (search, sort, pages, row selection) and
|
|
140
|
+
SVG charts (bar, line, area, pie, sparkline) drawn on the server. Each
|
|
141
|
+
behaviour module loads only on pages that use it; `from pyweb.ui import *`
|
|
142
|
+
imports them all.
|
|
143
|
+
- **Motion** (in the spirit of motion.dev): `initial=`, `animate=`,
|
|
144
|
+
`exit=` and `transition=` on any element, springs that keep their
|
|
145
|
+
momentum when interrupted, presets (`animate="fade-up"`), variants with
|
|
146
|
+
staggered children, `while_hover`/`while_tap`/`while_focus`/
|
|
147
|
+
`while_in_view`, exit animations for `if` and `for`, `layout` (rows glide
|
|
148
|
+
to their new places, keeping focus and input values), `layout_id`
|
|
149
|
+
(shared elements), `drag` with constraints and momentum, counting
|
|
150
|
+
numbers (`animate="count"`), View Transitions on client navigation, and
|
|
151
|
+
`from pyweb.motion import animate, timeline, stagger, scroll_progress,
|
|
152
|
+
use_transform` for browser code. `App(motion=Motion(feel=, speed=,
|
|
153
|
+
reduced=))` changes every animation at once, the UI kit's included, and
|
|
154
|
+
reduced-motion settings are followed. The server renders the first
|
|
155
|
+
pose; pages without motion don't load the engine (7.5 KB gzipped).
|
|
156
|
+
- **Customising the kit, every level.**
|
|
157
|
+
- The dev toolbar gets a **Theme** tab: a live preview on the running
|
|
158
|
+
app, copy as code, and "Save to app.pyweb", which rewrites only the
|
|
159
|
+
`App(theme=...)` argument.
|
|
160
|
+
- It also gets a **Motion** tab: pick an animated element, tune its
|
|
161
|
+
spring or duration, and replay it.
|
|
162
|
+
- `ui.extend("Button", variant={"brand": {...}})` adds your own
|
|
163
|
+
variants to kit components.
|
|
164
|
+
- `variants()`, `style()` and `cx()` (cva and tailwind-merge style)
|
|
165
|
+
are for your own components, on the server and in the browser.
|
|
166
|
+
- The editor completes variant, size and tone values, presets,
|
|
167
|
+
`Theme(...)`/`Motion(...)` settings and transition keys, and warns
|
|
168
|
+
about unknown ones or out-of-range springs.
|
|
169
|
+
- **A "UI kit and motion" example** (`examples/ui`): dialogs, menus,
|
|
170
|
+
pickers, toasts, a data table and charts, plus staggered, gesture,
|
|
171
|
+
drag, layout and shared-element animations. It runs on the website, as
|
|
172
|
+
a live demo and in the playground.
|
|
173
|
+
- `MenuItem(href=...)` makes a menu item a link.
|
|
174
|
+
- **The UI kit is tested in Chromium, Firefox and WebKit** (keyboard
|
|
175
|
+
behaviour of every component, motion and the examples), with
|
|
176
|
+
screenshot baselines per component in light and dark, and axe-core
|
|
177
|
+
finding no serious violations on the examples, the auth pages and
|
|
178
|
+
`/admin`.
|
|
179
|
+
- **Accessibility checks** in `pyweb check` and the editor: images
|
|
180
|
+
without alt, clicks keyboards can't reach, unlabelled fields, unnamed
|
|
181
|
+
buttons and links, positive tabindex, an empty page language. The
|
|
182
|
+
examples and templates were fixed where they fell short.
|
|
183
|
+
- **Testing apps with organizations:** `client.login(email, org="Acme",
|
|
184
|
+
org_role="admin")` signs in as a member of that organization (created
|
|
185
|
+
if needed) and makes it the current one; `with as_org(org):` runs code
|
|
186
|
+
for an organization; `Factory(...).for_org(org)` makes tenant rows in it.
|
|
187
|
+
- **`@app.page(..., org_role=..., feature=...)`** (and on layouts), like
|
|
188
|
+
`@server`: the page needs that role in the current organization, or a
|
|
189
|
+
plan with that feature (403 otherwise).
|
|
190
|
+
- **Editor:** completions for `@server(...)` and `@app.page(...)` options,
|
|
191
|
+
`org_role=`, `per=`, `rate=`, plan features, `tenant=True` and webhook
|
|
192
|
+
schemes; warnings for a malformed `rate=`, an unknown role, and a
|
|
193
|
+
`tenant=True` Model read with `.unscoped()` outside `with system():`.
|
|
194
|
+
- **A much better MCP server** (29 tools, up from 18):
|
|
195
|
+
- new tools: `pyweb_docs` (search every docs page, the guide and the
|
|
196
|
+
threat model), `pyweb_ui` (the kit's components, their parameters and
|
|
197
|
+
source, copying one into your app, making and saving a theme),
|
|
198
|
+
`pyweb_review` (screenshots in light and dark at desktop and phone
|
|
199
|
+
widths with axe-core's audit), `pyweb_explain` (what your editor shows
|
|
200
|
+
for a line: where each name runs, problems, completions),
|
|
201
|
+
`pyweb_profile`, `pyweb_webhook_send` (a correctly signed test
|
|
202
|
+
delivery, then the handler's result), `pyweb_teams`, `pyweb_upgrade`,
|
|
203
|
+
`pyweb_deploy_plan`, `pyweb_openapi` and `pyweb_security_check`;
|
|
204
|
+
- `pyweb_dev` and `pyweb_logs` see your running `pyweb dev`: whether the
|
|
205
|
+
app compiles (with the traceback), its logs and the requests your
|
|
206
|
+
browser made. `pyweb dev` writes `.pyweb/dev.json` (its address and a
|
|
207
|
+
token readable only by you); `pyweb_screenshot` and `pyweb_review` use
|
|
208
|
+
it too;
|
|
209
|
+
- `pyweb_render`, `pyweb_call`, `pyweb_screenshot` and `pyweb_profile`
|
|
210
|
+
act as a signed-in user and organization member (`as_user`, `roles`,
|
|
211
|
+
`org`, `org_role`);
|
|
212
|
+
- MCP 2025-11-25: every tool has a title, read-only/destructive hints
|
|
213
|
+
and an output schema; arguments are checked before a tool runs; slow
|
|
214
|
+
tools report progress; logging; completion of resource and prompt
|
|
215
|
+
arguments; resources for every docs page (`pyweb://docs/<page>`), kit
|
|
216
|
+
component (`pyweb://kit/<Component>`) and the threat model;
|
|
217
|
+
- eight new prompts: `add_teams`, `make_installable`, `make_desktop_app`,
|
|
218
|
+
`security_review`, `upgrade_pyweb`, `design_pass`, `fix_failing_test`
|
|
219
|
+
and `debug_page`;
|
|
220
|
+
- `pyweb mcp --http` serves Streamable HTTP on 127.0.0.1 (sessions,
|
|
221
|
+
event streams for progress; `Origin` and `Host` checked; a token is
|
|
222
|
+
required to listen anywhere else).
|
|
223
|
+
- The documentation ships inside the package, for the MCP server.
|
|
224
|
+
- **`pyweb upgrade`** points at stylesheets written for the 0.5 sign-in and
|
|
225
|
+
admin pages' classes.
|
|
226
|
+
- **Playground:** apps with teams get a "Team member" visitor next to
|
|
227
|
+
"Signed in" (both in one demo team, so you can see what each role sees),
|
|
228
|
+
and installable apps get an Install tab with their manifest and icons.
|
|
229
|
+
- **Docs:** [Upgrading to 0.6](docs/27-upgrading.md), testing apps with
|
|
230
|
+
teams, and the SaaS tutorial rewritten around teams.
|
|
231
|
+
- **New `desktop` template** (`pyweb new notes --template desktop`): a
|
|
232
|
+
Markdown notes app in its own window that exports a note with the
|
|
233
|
+
system's Save dialog, with its tests and packaging instructions.
|
|
234
|
+
- **Plain functions from other `.pyweb` files work in browser code**,
|
|
235
|
+
along with the functions and constants they use.
|
|
236
|
+
- **`math` in browser code.** `import math` (and `from math import pi`)
|
|
237
|
+
works in browser code, translated to JavaScript's `Math`.
|
|
238
|
+
- **Components pass attributes through.** A `**attrs` parameter takes any
|
|
239
|
+
other attribute, and `{**attrs}` spreads a dict onto an element
|
|
240
|
+
(explicit attributes win, classes merge).
|
|
241
|
+
- **Desktop apps.** `pyweb desktop app.pyweb` opens the app in its own
|
|
242
|
+
window (pywebview: WebView2, WKWebView, WebKitGTK or Qt), with its
|
|
243
|
+
database, uploads and per-installation secrets in the OS's data folder.
|
|
244
|
+
Only the window gets in: a one-time launch cookie, an exact `Host` and
|
|
245
|
+
same-origin requests are required, and links to other sites open in the
|
|
246
|
+
browser. `pyweb.desktop` gives server code file dialogs, notifications,
|
|
247
|
+
"reveal in folder" and the data folder; `App(desktop=Window(...))` sets
|
|
248
|
+
the window. `pyweb build --desktop [--package]` writes a Briefcase
|
|
249
|
+
project with icons for every OS and builds the installer (MSI, DMG,
|
|
250
|
+
`.deb`/`.rpm`). `pyweb-stack[desktop]` installs what it needs.
|
|
251
|
+
- **Push notifications.** A `data-pw-push="subscribe"` button asks for
|
|
252
|
+
permission; `push.send(user, "Title", "Body", url="/orders/42")` encrypts
|
|
253
|
+
(RFC 8291), signs (VAPID) and delivers through background jobs, deleting
|
|
254
|
+
subscriptions the push service says are gone. Only the browsers' push
|
|
255
|
+
services are accepted as endpoints. `pyweb push key` makes a
|
|
256
|
+
`PYWEB_VAPID_KEY`; `pyweb-stack[push]` installs what it needs.
|
|
257
|
+
- **Uploaded files are cleaned up.** Files no row refers to are deleted
|
|
258
|
+
after a day (daily, or `pyweb files clean`).
|
|
259
|
+
- **API tokens and OpenAPI.** `/account/tokens` makes tokens for calling
|
|
260
|
+
server functions with `Authorization: Bearer` (scoped to functions,
|
|
261
|
+
optionally expiring, stored as hashes); `pyweb openapi app.pyweb` writes
|
|
262
|
+
an OpenAPI 3.1 document from the functions' types, rules and guards.
|
|
263
|
+
- **Your data.** `/account` offers a JSON download of everything stored
|
|
264
|
+
about the user (`auth.export_user()`) and deleting the account
|
|
265
|
+
(`auth.delete_user()`, after a recent sign-in and typing the email).
|
|
266
|
+
- **Signed-in devices.** `/account` lists each session (device, network,
|
|
267
|
+
last seen) and can sign one out; `auth.sessions(user)` and
|
|
268
|
+
`auth.end_session(user, id)` do the same from code. Apps with migrations
|
|
269
|
+
need one for the new `auth_sessions` table (`pyweb upgrade` says so);
|
|
270
|
+
until then sign-in works as before.
|
|
271
|
+
- **Supply chain.** `pyweb build` writes a CycloneDX `sbom.json`; releases
|
|
272
|
+
get PEP 740 attestations on PyPI and a CycloneDX file; CI pins every
|
|
273
|
+
GitHub Action to a commit.
|
|
274
|
+
- **Teams.** `app.use_auth(orgs=True)` adds organizations, members with
|
|
275
|
+
roles (owner, admin, member, viewer), emailed invitations and the pages
|
|
276
|
+
to manage them (`/orgs`). `class Project(Model, tenant=True)` keeps each
|
|
277
|
+
organization's rows to itself: queries, saves, bulk updates, live lists
|
|
278
|
+
and background jobs are all limited to the current organization.
|
|
279
|
+
`@server(org_role="admin", feature="exports")` and `rate=..., per="org"`;
|
|
280
|
+
`auth.plans({...})` with features and limits (seats are enforced).
|
|
281
|
+
On Postgres, migrations add row-level security, so raw SQL is limited to
|
|
282
|
+
the organization too. See [Teams](docs/30-teams.md).
|
|
283
|
+
- **`THREAT_MODEL.md`**: every threat, its mitigation and the test that
|
|
284
|
+
proves it (a test keeps the references current). CI fuzzes the RPC and
|
|
285
|
+
form parsers with Hypothesis and runs an OWASP ZAP baseline scan.
|
|
286
|
+
- **CSP violation reports** go to `/__pyweb/csp-report`: a `pyweb.csp` log
|
|
287
|
+
line and the `pyweb_csp_violations_total` metric.
|
|
288
|
+
|
|
289
|
+
### Changed
|
|
290
|
+
- **About 2.5× more requests per second** over `pyweb serve`: pages render
|
|
291
|
+
on the thread that read the request (the time limit moved into the
|
|
292
|
+
connection handler), each page's markup compiles to one Python function,
|
|
293
|
+
and request ids no longer cost a system call each.
|
|
294
|
+
- **`import pyweb` takes 2 ms** instead of 87 ms, and `pyweb --version`
|
|
295
|
+
about a third of the time it did: modules load when first used.
|
|
296
|
+
- **The sign-in, account, organization and admin pages use the UI kit**
|
|
297
|
+
and the app's theme: `App(theme=...)` restyles them along with your
|
|
298
|
+
pages. `/__pyweb/auth/auth.css` now only lays them out.
|
|
299
|
+
- **The templates and examples use the UI kit and motion.** `todo` rows
|
|
300
|
+
animate in and out and filter with a toggle group; `counter` counts;
|
|
301
|
+
`saas` (Teamboard) has a sidebar layout with an account menu, a
|
|
302
|
+
new-project dialog, toasts, animated live tasks, empty states and the
|
|
303
|
+
zinc theme; the dashboard's totals count up and it adds the kit's bar
|
|
304
|
+
chart and a sparkline. The shared `static/app.css` only lays pages out.
|
|
305
|
+
- **The `saas` template (Teamboard) has teams.** Projects and tasks are
|
|
306
|
+
`tenant=True` Models shared by an organization; each new user gets a
|
|
307
|
+
team, and the sidebar links to members and invitations and to API
|
|
308
|
+
tokens. Creating a project publishes a `project.created` webhook, and
|
|
309
|
+
the app is installable (`pwa=True`). Its tests show two teams kept apart
|
|
310
|
+
and two teammates sharing projects.
|
|
311
|
+
- `pyweb check` no longer asks for a policy on `tenant=True` Models: the
|
|
312
|
+
organization scope already limits who reads them.
|
|
313
|
+
- The CI benchmark measures fixed copies of the todo and saas apps
|
|
314
|
+
(`bench/apps/`), so redesigning an example doesn't read as a speed
|
|
315
|
+
change.
|
|
316
|
+
- The website's live demos are what the server sends (with the kit's and
|
|
317
|
+
motion's files), and the playground runs apps that use the kit.
|
|
318
|
+
- Tracebacks from `.pyweb` files show the source lines.
|
|
319
|
+
- `pyweb check` reports the line of a Python syntax error.
|
|
320
|
+
- **`pyweb.platform` is real and supported:** `platform.desktop`,
|
|
321
|
+
`platform.pwa`, `platform.installed` and `platform.web` are read per
|
|
322
|
+
request. `platform.mobile` and `platform.edge` are always False (they
|
|
323
|
+
were placeholders); `pyweb upgrade` points at them.
|
|
324
|
+
|
|
325
|
+
### Fixed
|
|
326
|
+
- A server function argument annotated `date`, `datetime`, `time`,
|
|
327
|
+
`Decimal` or `UUID` arrived as the text the browser sent, unchecked
|
|
328
|
+
(`"banana"` for a `date`). It's now parsed (ISO 8601 for dates and
|
|
329
|
+
times), and anything else is a `validation_error` naming the field.
|
|
330
|
+
- `checked={row["done"]}` with a database's `0` rendered `checked="0"`, so
|
|
331
|
+
the server showed every box ticked. Boolean attributes (`checked`,
|
|
332
|
+
`disabled`, `selected`, ...) now follow truthiness on the server as in
|
|
333
|
+
the browser.
|
|
334
|
+
- A page using only components copied with `pyweb ui add` didn't get the
|
|
335
|
+
kit's stylesheet and behaviours.
|
|
336
|
+
- An alert's title was smaller than its text.
|
|
337
|
+
- The plant shop example's link and dark-mode button colours were below
|
|
338
|
+
WCAG contrast.
|
|
339
|
+
- A `Card` (or `Alert`, `Table`, `Breadcrumb`) inside `Tabs` had its
|
|
340
|
+
content taken for a tab panel and hidden; and the card's header styles
|
|
341
|
+
reached a data table's header row inside it.
|
|
342
|
+
- The saas template's daily digest called a query method that doesn't
|
|
343
|
+
exist (`values_list`); it uses `pluck`, and a test runs it.
|
|
344
|
+
- A menubar arrow key pressed right after opening a menu (before focus
|
|
345
|
+
moved into it) left that menu open.
|
|
346
|
+
- `lambda n=n: go(n)` in an event handler kept the event instead of `n`.
|
|
347
|
+
- Hydrating a page no longer rewrites attributes the server already
|
|
348
|
+
rendered, so it can't undo what a UI kit script (or the user, typing
|
|
349
|
+
early) changed in the meantime.
|
|
350
|
+
- Markup right after an `if`/`for` block inside an element (a
|
|
351
|
+
`{children}` or text line at the block's indent) was put inside the
|
|
352
|
+
block; it now follows it.
|
|
353
|
+
- An optional callback prop that wasn't passed no longer throws when its
|
|
354
|
+
event fires.
|
|
355
|
+
- Markup right after a `def` whose signature spans several lines failed to
|
|
356
|
+
compile.
|
|
357
|
+
- Text children re-rendered in the browser showed as a list (`['Medium']`).
|
|
358
|
+
- `get_or_404()` inside a server function answered 500; it now answers 404.
|
|
359
|
+
- `Post.id` (and other columns added automatically) failed before the Model
|
|
360
|
+
had been used once.
|
|
361
|
+
- Log redaction took time quadratic in the length of a word without `=`
|
|
362
|
+
(0.3 ms for a 64-character id, on every logged query parameter while
|
|
363
|
+
developing); it is now linear. Found by the new benchmark gate.
|
|
364
|
+
- Form submits with more than 1,000 fields or 100 files are refused (400)
|
|
365
|
+
instead of being parsed.
|
|
366
|
+
- A Model that fails to set up no longer breaks every Model defined after it.
|
|
367
|
+
- Models on Python 3.14+ (annotations are evaluated lazily there, PEP 649).
|
|
368
|
+
|
|
369
|
+
## [0.5.0]
|
|
370
|
+
|
|
371
|
+
Production apps in one Python file: Models with migrations, forms, an
|
|
372
|
+
auth kit with an admin, row-level live updates over WebSockets on PyWeb's
|
|
373
|
+
own server, durable background jobs and schedules, one-command
|
|
374
|
+
deployment for six targets, built-in observability, and editors and AI
|
|
375
|
+
assistants that understand your data. Most 0.4 apps run unchanged; see
|
|
376
|
+
[Upgrading to 0.5](docs/27-upgrading.md) and run `pyweb upgrade --check`.
|
|
377
|
+
|
|
378
|
+
### Added
|
|
379
|
+
- **Models on `pyweb.db`.** `class Post(Model): title: str = Field(max=120)`
|
|
380
|
+
works on SQLite, Postgres and MySQL with the same code. Types include
|
|
381
|
+
`Decimal`, timezone-aware `datetime`, `date`, `time`, JSON (`dict`/`list`),
|
|
382
|
+
`bytes`, `UUID`, `Text`, `Email`, `URL` and `Slug`. `Field(...)` holds the
|
|
383
|
+
column's schema and its validation rules (`min`, `max`, `pattern`,
|
|
384
|
+
`choices`, `format`, `unique`, `index`, `private`, `auto_now_add`, ...);
|
|
385
|
+
`@validates("field")` adds custom checks. Saving writes only changed
|
|
386
|
+
columns; unique violations become field errors.
|
|
387
|
+
- **Relations.** `author: User` is a foreign key, `tags: list[Tag]` is
|
|
388
|
+
many-to-many, `OneToOne(...)`, with reverse accessors (`user.posts`).
|
|
389
|
+
`include("author", "tags", "author.profile")` loads related rows with one
|
|
390
|
+
query per relation; reading a relation that wasn't included is an error
|
|
391
|
+
while developing (so pages can't do N+1 queries) and a logged warning in
|
|
392
|
+
production. Relation filters (`Post.author.has(...)`, `User.posts.any(...)`)
|
|
393
|
+
use subqueries.
|
|
394
|
+
- **Query builder.** Immutable, lazy queries with `Post.views > 10`, `&`,
|
|
395
|
+
`|`, `~`, keyword lookups (`title__icontains=`, `id__in=`, ...),
|
|
396
|
+
`order`, slicing, `first/last/get/get_or_404`, `count/exists`,
|
|
397
|
+
`values/pluck`, `aggregate` and `group_by` with `Count/Sum/Avg/Min/Max`,
|
|
398
|
+
bulk `update/delete`, cursor pagination with `page()`, `get_or_create`,
|
|
399
|
+
`upsert`, `bulk_create`, and `.live()`.
|
|
400
|
+
- **Transactions per request.** Every `@server` call is one transaction,
|
|
401
|
+
started at its first write; `@atomic` / `@atomic(False)`. Nested
|
|
402
|
+
`db.transaction()` blocks are savepoints. `ValidationError` from an RPC
|
|
403
|
+
answers 422 with the errors per field.
|
|
404
|
+
- **Migrations.** `pyweb db diff` compares Models with the live database
|
|
405
|
+
and writes Python migrations; `upgrade`, `downgrade`, `status`, `adopt`,
|
|
406
|
+
`squash`, `seed`. Changes are split into *expand* steps (safe during a
|
|
407
|
+
rolling deploy) and *contract* steps (`upgrade --contract`), including
|
|
408
|
+
renames with `--rename table.old=new`. A lock shared by every server
|
|
409
|
+
(Postgres advisory lock, MySQL `GET_LOCK`, or a lock row) makes
|
|
410
|
+
`pyweb serve --migrate` safe with many servers. SQLite schema changes
|
|
411
|
+
rebuild tables with foreign keys paused. `pyweb dev` applies pending
|
|
412
|
+
migrations, and apps without migrations create tables on first use.
|
|
413
|
+
- **Forms.** `<Form action={save_post}>` with `<Input>`, `<Textarea>`,
|
|
414
|
+
`<Select>`, `<Checkbox>`, `<FileInput>`, `<Submit>` and `<FormError>`
|
|
415
|
+
builds labelled, accessible fields from the action's Model (or plain
|
|
416
|
+
typed parameters). Rules are checked in the browser while typing, with
|
|
417
|
+
the same messages as the server; the form submits without a page load and
|
|
418
|
+
shows server errors next to each input, and still works as a plain POST
|
|
419
|
+
without JavaScript (422 re-render with values, 303 on success). CSRF
|
|
420
|
+
token, cross-site refusal, a one-time key per submit (double clicks run
|
|
421
|
+
once), only the action's own fields are read, `values={row}` edits a row
|
|
422
|
+
through a signed id, and `forms.carry()`/`restore()` for multi-step forms.
|
|
423
|
+
The browser code (`forms.js`) only loads on pages with a form.
|
|
424
|
+
- **Uploads and storage.** `File(types=["image/*"], max_size="2MB")` fields
|
|
425
|
+
and `UploadedFile` parameters; types checked from the file's bytes;
|
|
426
|
+
`PYWEB_MAX_UPLOAD` (default 10MB); `PYWEB_STORAGE` is a local folder
|
|
427
|
+
(served with a sandboxing CSP) or S3-compatible storage with presigned
|
|
428
|
+
URLs and no SDK.
|
|
429
|
+
- **Auth kit.** `auth = app.use_auth()` adds `/signup`, `/login`,
|
|
430
|
+
`/logout`, `/reset`, `/account` and `/admin`, rendered on the server and
|
|
431
|
+
working without JavaScript. Sign in with a password, an emailed link, a
|
|
432
|
+
passkey (also offered in the email field's autofill) or GitHub / Google /
|
|
433
|
+
Microsoft (PKCE + `state`), with optional authenticator-app codes and
|
|
434
|
+
recovery codes. Define a page at the same route to replace one;
|
|
435
|
+
`auth.user()`, `auth.authenticate()`, `auth.login()`, `auth.set_roles()`,
|
|
436
|
+
`auth.revoke()` ... for custom flows. Tables `users`, `auth_identities`,
|
|
437
|
+
`auth_credentials`, `auth_tokens` and `auth_events` are ordinary Models.
|
|
438
|
+
- **Guards.** `login=True`, `roles=[...]` and `fresh=seconds` on
|
|
439
|
+
`@app.page`, `@app.layout` and `@server`.
|
|
440
|
+
- **Row policies.** `Note.policy(read=..., write=...)` limits the rows each
|
|
441
|
+
user can see and change, on every query and save in a request
|
|
442
|
+
(`with system():` skips them on purpose).
|
|
443
|
+
- **Admin.** `/admin` lists, searches, edits and deletes rows of every
|
|
444
|
+
Model for users with the `admin` role; private fields never appear; every
|
|
445
|
+
change is in the audit log.
|
|
446
|
+
- **Account security.** scrypt (or argon2id with `pyweb-stack[auth]`)
|
|
447
|
+
hashes upgraded at sign-in; breached-password check in production;
|
|
448
|
+
guessing slowed down per account and per IP (shared through Redis) with no
|
|
449
|
+
lockout; the same answer and timing for unknown emails; emailed links
|
|
450
|
+
hashed, single-use (also under concurrent clicks) and short-lived;
|
|
451
|
+
passkey challenges single-use on every server; password resets, password
|
|
452
|
+
and role changes sign out other sessions, durably in the `users` table
|
|
453
|
+
(and fail closed if the database can't be checked); emailed links are
|
|
454
|
+
built from `PYWEB_ORIGIN`, never from the `Host` header, in production.
|
|
455
|
+
- **`pyweb.mail`.** `PYWEB_MAIL_URL` (SMTP/SMTPS) and `PYWEB_MAIL_FROM`;
|
|
456
|
+
while developing, emails are printed and kept in `pyweb.mail.OUTBOX`.
|
|
457
|
+
- `pyweb check --production` reports `use_auth()` without `PYWEB_ORIGIN`.
|
|
458
|
+
- **PyWeb's own server.** `pyweb serve` and `pyweb dev` now run on
|
|
459
|
+
`pyweb.net`: HTTP/1.1, WebSockets (RFC 6455) and Server-Sent Events on
|
|
460
|
+
one asyncio loop per process, standard library only. Thousands of open
|
|
461
|
+
live connections cost a coroutine each instead of a thread (the old
|
|
462
|
+
server stopped at 256); page renders and RPCs run on a thread pool
|
|
463
|
+
(`PYWEB_THREADS`). `--workers N` / `WEB_CONCURRENCY` forks processes
|
|
464
|
+
sharing the port and replaces crashed ones. The parser refuses request
|
|
465
|
+
smuggling vectors (CL+TE, repeated lengths, folded headers, bad chunk
|
|
466
|
+
sizes, bare LF) and has timeouts for heads, bodies, idle keep-alive and
|
|
467
|
+
slow readers; SIGTERM drains gracefully.
|
|
468
|
+
- **One WebSocket per tab for live updates.** Every `subscribe()` and live
|
|
469
|
+
query on a page shares `/__pyweb/ws` (Server-Sent Events used one
|
|
470
|
+
connection each, and browsers allow six per site), with jittered
|
|
471
|
+
reconnects that resume where they stopped, and automatic fallback to
|
|
472
|
+
Server-Sent Events, then polling. The socket checks `Origin`
|
|
473
|
+
(`PYWEB_ALLOWED_ORIGINS` for others), re-checks the session every minute
|
|
474
|
+
and closes when it's revoked, limits subscriptions, message size and
|
|
475
|
+
rate, and doesn't buffer without limit for slow clients. Works in the
|
|
476
|
+
ASGI adapter too.
|
|
477
|
+
- **Live queries send patches.** Rows with an `id` (or `key=`) travel as
|
|
478
|
+
inserts, updates, deletes and moves: changing one row of a long list
|
|
479
|
+
sends that row only, and the browser redraws only it. A page that missed
|
|
480
|
+
a change gets the whole result once. `Model.query().live()` uses the
|
|
481
|
+
primary key.
|
|
482
|
+
- **Presence.** `presence(name)` and `join(room, info, on_members,
|
|
483
|
+
on_cast)`: who is on a page, and ephemeral casts (cursors, typing) to
|
|
484
|
+
everyone else in the room, across processes with Redis.
|
|
485
|
+
- Feeds made while someone is signed in only work for that session.
|
|
486
|
+
- `live.js`: live-update code loads only on pages that use it.
|
|
487
|
+
- **Durable background jobs.** `@app.job(retries=, timeout=, queue=,
|
|
488
|
+
unique_for=)` and `fn.enqueue(..., delay=, at=, key=)`. Jobs are stored
|
|
489
|
+
in the app's database (`pyweb_jobs`), Redis (`PYWEB_JOBS=redis`) or memory,
|
|
490
|
+
and queued inside the request's transaction (a rollback queues nothing).
|
|
491
|
+
Workers claim with leases (`FOR UPDATE SKIP LOCKED` on Postgres/MySQL), so
|
|
492
|
+
a crashed or killed worker's job runs again elsewhere; failures retry with
|
|
493
|
+
jittered exponential backoff, then go `dead` (retry from `/admin` or
|
|
494
|
+
`pyweb jobs retry`); timeouts; graceful SIGTERM hands running jobs back.
|
|
495
|
+
`pyweb worker`, `pyweb jobs list|retry|purge|run`, and a worker inside
|
|
496
|
+
`pyweb dev`/`pyweb serve` (`PYWEB_WORKER=0` to run them separately).
|
|
497
|
+
- **Schedules.** `@app.cron("0 3 * * *", tz=..., catchup=...)` with PyWeb's
|
|
498
|
+
own cron parser (daylight saving handled) and `@app.every(minutes=5)`.
|
|
499
|
+
Every server runs the scheduler and each slot is queued exactly once,
|
|
500
|
+
with no leader election.
|
|
501
|
+
- **Email outbox.** With durable jobs, `pyweb.mail.send` queues delivery as
|
|
502
|
+
a retried job that only leaves if the request committed.
|
|
503
|
+
- **Nightly cleanup** of finished jobs, expired sign-in links and old audit
|
|
504
|
+
entries.
|
|
505
|
+
- `db.after_commit(fn)`: run code once the current transaction commits
|
|
506
|
+
(dropped on rollback, including a rolled-back savepoint), and
|
|
507
|
+
`db.after_end(fn)` for code that must run either way.
|
|
508
|
+
- Jobs stored in the app's database are marked done in the job's own
|
|
509
|
+
transaction, so a job's database writes happen exactly once even if a
|
|
510
|
+
worker crashes or loses its lease.
|
|
511
|
+
- **`pyweb deploy` plans the production topology.** It reads the app
|
|
512
|
+
(database, jobs, live updates, sign-in, uploads, migrations), decides
|
|
513
|
+
what production needs for the target and number of machines (Postgres
|
|
514
|
+
instead of SQLite for several machines or disposable disks, Redis for
|
|
515
|
+
more than one process, a separate worker, migrations once per deploy,
|
|
516
|
+
object storage for uploads, the secrets to set), prints the plan with
|
|
517
|
+
the reason for each decision, and writes the files. Targets: `docker`,
|
|
518
|
+
`compose` (Caddy, load-balanced replicas, Postgres, Redis, worker,
|
|
519
|
+
migrate step), `k8s` (web and worker Deployments, migrate init
|
|
520
|
+
container, probes, PodDisruptionBudget, HorizontalPodAutoscaler,
|
|
521
|
+
Ingress), `fly`, `render` and `railway`. `--plan` prints only;
|
|
522
|
+
`--check` fails CI when the plan has warnings.
|
|
523
|
+
- **A production image**: multi-stage, non-root, cached pip installs,
|
|
524
|
+
health check, `SIGTERM` drain, `PYTHON_IMAGE`/`PYWEB_SPEC`/
|
|
525
|
+
`EXTRA_PACKAGES` build arguments.
|
|
526
|
+
- `DATABASE_URL` overrides the URL in `App(database=...)` (develop on
|
|
527
|
+
SQLite, deploy on Postgres, same code). `pyweb serve` uses `$PORT`.
|
|
528
|
+
- `pyweb check --production` also reports Models without migrations,
|
|
529
|
+
unapplied migrations, email without `PYWEB_MAIL_URL`, uploads without
|
|
530
|
+
object storage, and jobs with `PYWEB_WORKER=0`.
|
|
531
|
+
- CI deploys a real stack (2 replicas, worker, Postgres, Redis, Caddy)
|
|
532
|
+
with `pyweb deploy compose` and checks load balancing, jobs on the
|
|
533
|
+
worker and live updates across replicas.
|
|
534
|
+
- **Read replicas** with `DATABASE_REPLICA_URL` (read-your-writes inside a
|
|
535
|
+
request), slow query logging with query plans (`PYWEB_SLOW_QUERY_MS`),
|
|
536
|
+
`pyweb.db.QUERY_HOOKS`, seeds (`pyweb.db.seeds`) and
|
|
537
|
+
`pyweb.testing.Factory`.
|
|
538
|
+
|
|
539
|
+
- **Observability.** A dev toolbar on every page in `pyweb dev`: the SQL each
|
|
540
|
+
request ran (repeated statements grouped, with an N+1 warning), spans,
|
|
541
|
+
jobs queued, emails captured, errors, and a list of every request since;
|
|
542
|
+
it never ships in production builds. JSON logs in production (readable
|
|
543
|
+
text while developing) where every line carries the request id, trace
|
|
544
|
+
id, route and user, and secrets (`password`, `token`, `api_key`, ... plus
|
|
545
|
+
`PYWEB_LOG_REDACT`) are redacted, including inside messages and URLs.
|
|
546
|
+
Prometheus `/metrics` (`PYWEB_METRICS_TOKEN`; requests, server functions,
|
|
547
|
+
queries, jobs, live sockets, mail, sign-ins, errors), added up across
|
|
548
|
+
`--workers` processes; `pyweb worker --metrics-port`. W3C `traceparent`
|
|
549
|
+
is continued, returned on every response and carried into the jobs a
|
|
550
|
+
request queues, so one trace covers the click and the job's work on
|
|
551
|
+
another machine. OpenTelemetry spans for requests, queries, jobs and
|
|
552
|
+
`with span("..."):` blocks (`pip install pyweb-stack[otel]`,
|
|
553
|
+
`PYWEB_OTEL=1`). `@app.on_error` hooks receive every unhandled error with
|
|
554
|
+
its request context.
|
|
555
|
+
- `pyweb worker --alive`: a health check for worker containers (the worker
|
|
556
|
+
touches a heartbeat file after each round that reached its job store);
|
|
557
|
+
compose and Kubernetes use it.
|
|
558
|
+
|
|
559
|
+
- **`pyweb new --template saas`**: accounts, projects and tasks with row
|
|
560
|
+
policies, `<Form>`s, live pages, a background job, a weekday cron email,
|
|
561
|
+
`/admin`, seeds, tests and a first migration, ready for `pyweb deploy`.
|
|
562
|
+
Templates with a database get `migrations/0001_initial.py` and a README.
|
|
563
|
+
- `TestClient.login(email, roles=[...])` signs a test in (creating the
|
|
564
|
+
account with the auth kit) without going through the sign-in form;
|
|
565
|
+
`logout()` signs out.
|
|
566
|
+
- `seeds.py` sees the app's Models by name; `pyweb.models.model("Post")`
|
|
567
|
+
looks one up anywhere.
|
|
568
|
+
- A server function parameter can take its rules from a default:
|
|
569
|
+
`title: str = Field(min=1, max=120)` (same as `Annotated[str, Field(...)]`).
|
|
570
|
+
- `<Input name="project_id" type="hidden" />` is just the input (for ids the
|
|
571
|
+
page sets with `values=`); `label=""` hides a field's label but keeps it as
|
|
572
|
+
the input's `aria-label`.
|
|
573
|
+
|
|
574
|
+
- **Editors know your data.** The language server completes Model fields
|
|
575
|
+
and query methods after `Post.`, keyword fields in `where(`/`create(`,
|
|
576
|
+
relations in `include("`, columns in `order("`, `Field(` options and a
|
|
577
|
+
`<Form>`'s fields in `name="`. It flags form fields the action doesn't
|
|
578
|
+
have, loops that read a relation their query didn't `include()` (N+1,
|
|
579
|
+
pointing at the line), and Models changed without a migration, with a
|
|
580
|
+
code lens that writes it. VS Code: "Create Migration from Model Changes",
|
|
581
|
+
"Apply Migrations", and snippets for Models, relations, form pages, live
|
|
582
|
+
pages, jobs, schedules, row policies and `use_auth`. The extension uses
|
|
583
|
+
the current language client (vscode-languageclient 10), is bundled
|
|
584
|
+
(12 files instead of 364), needs VS Code 1.91 or later, and is tested in
|
|
585
|
+
a real VS Code against the real language server in CI.
|
|
586
|
+
- **`pyweb db check`**: fails (exit 1) when the Models changed without a
|
|
587
|
+
migration, by applying the migrations to a scratch database, so it needs
|
|
588
|
+
no real database. For CI.
|
|
589
|
+
- **MCP tools for data**: `pyweb_db_schema` (Models, tables, drift),
|
|
590
|
+
`pyweb_db_query` (read-only SQL, capped, secrets redacted),
|
|
591
|
+
`pyweb_migrations` (status, diff, upgrade), `pyweb_jobs` (list, retry,
|
|
592
|
+
run) and `pyweb_requests` (each request's SQL, N+1 warnings, jobs, errors).
|
|
593
|
+
|
|
594
|
+
- **Playground 2.** Several files per app (import components from a second
|
|
595
|
+
`.pyweb` file; shared as one link, downloaded as a zip), a Requests tab
|
|
596
|
+
(each request's SQL with N+1 warnings, spans, jobs, emails, errors), a
|
|
597
|
+
Database tab (tables and rows, secrets redacted), a visitor picker to see
|
|
598
|
+
pages signed out, signed in or as an admin, background jobs run right
|
|
599
|
+
after the request that queued them, and SQLite loaded when an app has a
|
|
600
|
+
database. New examples: the blog (on Models) and the saas template.
|
|
601
|
+
- `Worker.run_inline()` runs due jobs on the calling thread (no thread pool).
|
|
602
|
+
|
|
603
|
+
- **`pyweb upgrade --check/--fix`** lists what an app needs to change for
|
|
604
|
+
0.5, with file and line, and applies the safe rewrites:
|
|
605
|
+
`Model.configure(...)` → `App(database=...)`, and `pyweb db
|
|
606
|
+
migrate/rollback` and `pyweb deploy --target X` in Procfiles, scripts,
|
|
607
|
+
Dockerfiles and CI files.
|
|
608
|
+
- `PYWEB_STRICT_DEPRECATIONS=1` turns deprecation warnings into errors.
|
|
609
|
+
Deprecated in 0.5: `Model.configure()`, the in-memory `Queue`,
|
|
610
|
+
`RedisQueue` and `@task` (use `@app.job`), and `pyweb deploy
|
|
611
|
+
--target/--compose`.
|
|
612
|
+
- `pyweb check` warns about SQL built from strings passed to `execute()`,
|
|
613
|
+
pages that read `auth.user().x` without `login=True`, and Models owned by
|
|
614
|
+
a user without a row policy; `--strict` fails on any warning.
|
|
615
|
+
|
|
616
|
+
### Changed
|
|
617
|
+
- The blog template and example use a Model instead of raw SQL.
|
|
618
|
+
- `pyweb lsp` answers requests it doesn't handle with "method not found"
|
|
619
|
+
(it answered `null`), as LSP clients expect.
|
|
620
|
+
- `PYWEB_MAX_CONNECTIONS` defaults to 10,000 per process (it was 256 for
|
|
621
|
+
the threaded server, which `pyweb serve --app module:factory` still uses).
|
|
622
|
+
- `pyweb.testing.serve` runs PyWeb's server.
|
|
623
|
+
- SQLite connections enforce foreign keys and use write-ahead logging.
|
|
624
|
+
- `Model.configure()` is deprecated in favour of `App(database=...)` /
|
|
625
|
+
`DATABASE_URL`; 0.4-style Models keep working (rows still read like
|
|
626
|
+
dicts: `row["name"]`).
|
|
627
|
+
- In production, a Model with no database is an error instead of an
|
|
628
|
+
in-memory SQLite file.
|
|
629
|
+
|
|
630
|
+
### Security
|
|
631
|
+
- Sessions and feed tokens are verified only with keys derived per purpose.
|
|
632
|
+
The raw secret, still accepted in 0.4.4 for tokens from older versions,
|
|
633
|
+
is no longer a key.
|
|
634
|
+
- The default Content-Security-Policy allows inline `<style>` blocks by
|
|
635
|
+
their hashes instead of `'unsafe-inline'` (so injected markup can't add
|
|
636
|
+
a stylesheet that leaks page content), and adds `form-action 'self'`.
|
|
637
|
+
|
|
638
|
+
### Fixed
|
|
639
|
+
- Rules on server function parameters (`Annotated[str, Field(max=40)]`) were
|
|
640
|
+
ignored: a direct call could send any value, and forms didn't get the
|
|
641
|
+
matching browser checks. They're enforced on every call now, with errors
|
|
642
|
+
per field (422).
|
|
643
|
+
- `owner: User = Field(readonly=True)` made a plain column instead of a
|
|
644
|
+
foreign key; options given with `Field(...)` now apply to the relation.
|
|
645
|
+
- `pyweb db seed --database URL` wrote to the app's own database; apps
|
|
646
|
+
without migrations yet couldn't be seeded before `pyweb dev` created
|
|
647
|
+
their tables.
|
|
648
|
+
- `pyweb dev` didn't reload pages larger than 1 KB after a change: they were
|
|
649
|
+
gzipped before the reload script could be added.
|
|
650
|
+
- `Model.query()...live()` in a page made a static page: the compiler only
|
|
651
|
+
recognised `live(db, sql)`.
|
|
652
|
+
- A value computed from a live query (`left = len([t for t in tasks if not
|
|
653
|
+
t["done"]])`), or from any server-loaded list a handler changes, was
|
|
654
|
+
computed once on the server and never updated in the browser. It now
|
|
655
|
+
follows its source there.
|
|
656
|
+
- A form for a Model with a read-only field (`owner: User =
|
|
657
|
+
Field(readonly=True)`) was refused with "owner is required" before the
|
|
658
|
+
server function could set the field.
|
|
659
|
+
- Validation messages named the field twice ("body Body is required").
|
|
660
|
+
- Schedules in UTC (including PyWeb's nightly cleanup) no longer need a
|
|
661
|
+
time zone database, which Windows and Pyodide (the playground) don't
|
|
662
|
+
ship; other zones explain that `pip install tzdata` provides one.
|
|
663
|
+
- The server answered a refused connection (503) or a rejected request and
|
|
664
|
+
closed at once; unread request bytes then made the kernel reset the
|
|
665
|
+
connection, which could destroy the response. It now closes politely
|
|
666
|
+
(half-close, drain briefly).
|
|
667
|
+
- While developing, a Model's table made on first use inside a request that
|
|
668
|
+
then failed was rolled back but still counted as made ("no such table"
|
|
669
|
+
until a restart). Tables are now made when the app loads, and a lazily
|
|
670
|
+
made one only counts once its transaction commits.
|
|
671
|
+
- Audit log entries written during a request that rolls back are kept
|
|
672
|
+
(written when the transaction ends, committed or not), and a failing
|
|
673
|
+
audit insert can no longer abort the request's transaction.
|
|
674
|
+
- `Site(..., rate_limit=False)` (used by `pyweb.testing.serve`) passed
|
|
675
|
+
`False` to the server instead of turning rate limits off.
|
|
676
|
+
|
|
677
|
+
## [0.4.4]
|
|
678
|
+
|
|
679
|
+
Security and running in production. No app changes needed, and nobody
|
|
680
|
+
is signed out by the upgrade.
|
|
681
|
+
|
|
682
|
+
### Added
|
|
683
|
+
- **Signing keys per purpose, and rotation.** Sessions, live-update feeds
|
|
684
|
+
and live query specs are signed with separate keys derived from
|
|
685
|
+
`PYWEB_AUTH_SECRET`. `PYWEB_AUTH_SECRET_PREVIOUS` keeps tokens from an
|
|
686
|
+
old secret working while you change it. Tokens from 0.4.3 and earlier
|
|
687
|
+
still work.
|
|
688
|
+
- **Sessions:** each login starts a new session; sessions renew on visits
|
|
689
|
+
and end 30 days after sign-in; `auth.revoke_user(user_id)` signs a user
|
|
690
|
+
out on every device (in memory, or shared with `RedisSessionVersions`).
|
|
691
|
+
- **Typed server function arguments:** `list[...]`, `dict[...]`,
|
|
692
|
+
`Optional`, `Literal`, dataclasses, Models and `Email` are checked
|
|
693
|
+
against their type hints, and unknown arguments are refused (422).
|
|
694
|
+
- **`pyweb/config.py`:** every `PYWEB_*` setting in one place, validated
|
|
695
|
+
at startup (a bad value stops the server with a clear message).
|
|
696
|
+
- **`PYWEB_REDIS_URL`:** shares rate limits (`RedisRateLimiter`) and live
|
|
697
|
+
updates between server processes.
|
|
698
|
+
- **`/readyz`** checks that every database the app opened answers (503
|
|
699
|
+
when one doesn't); `/healthz` stays a liveness check.
|
|
700
|
+
- **Graceful shutdown:** on SIGTERM and ASGI shutdown, live connections
|
|
701
|
+
end and background jobs get up to 10 seconds to finish.
|
|
702
|
+
- **`pyweb deploy --target compose --domain example.com`** puts Caddy in
|
|
703
|
+
front of the app with automatic HTTPS, keeps the app reachable only
|
|
704
|
+
through it, and sets `PYWEB_TRUST_PROXY=1` and Secure cookies. The k8s
|
|
705
|
+
target trusts its Ingress and uses `/readyz` for readiness. The
|
|
706
|
+
deployment docs explain reverse proxies, with nginx and Caddy examples.
|
|
707
|
+
- **`pyweb check --production`** lists settings that would leave a gap
|
|
708
|
+
(secret, HTTPS cookies, proxy, Redis with several workers, ...).
|
|
709
|
+
- **npm supply chain:** `pyweb.lock` records a SHA-256 per vendored file
|
|
710
|
+
and `pyweb build` refuses files that changed.
|
|
711
|
+
- **CI:** dependency vulnerability audit (pip-audit), CodeQL code
|
|
712
|
+
scanning, Dependabot, and signed build provenance for release files.
|
|
713
|
+
- **A much better playground** on the website: syntax highlighting with
|
|
714
|
+
the error line marked (click the line number to jump to it), a Console
|
|
715
|
+
that shows `print()` output, every server call with its arguments,
|
|
716
|
+
status and time, and errors from the page; a draggable divider;
|
|
717
|
+
phone/tablet/desktop preview widths and a reload button; highlighted
|
|
718
|
+
JavaScript with its gzipped size; Reset, Download (`app.pyweb`, ready
|
|
719
|
+
for `pyweb dev`) and autosave, so edits survive a reload; and a
|
|
720
|
+
Code/Preview switch on phones.
|
|
721
|
+
- Docs: a threat model, key rotation, session lifetime, a production
|
|
722
|
+
checklist, and the new settings.
|
|
723
|
+
|
|
724
|
+
### Security
|
|
725
|
+
- Requests the browser marks `Sec-Fetch-Site: cross-site` can't call
|
|
726
|
+
server functions.
|
|
727
|
+
- Deeply nested JSON bodies crashed the request handler with a
|
|
728
|
+
`RecursionError`; bodies deeper than 32 levels or with more than
|
|
729
|
+
10,000 values are now refused.
|
|
730
|
+
- `pyweb serve` and `pyweb dev` drop clients that stall (30 s), answer
|
|
731
|
+
503 past 256 simultaneous connections and 431 to headers over 16 KB.
|
|
732
|
+
- At most 20 open live connections per client address (429 beyond).
|
|
733
|
+
- Pages that take longer than 30 seconds to render answer 504 instead of
|
|
734
|
+
holding a thread.
|
|
735
|
+
- New response headers: `Permissions-Policy`, `Cross-Origin-Opener-Policy`,
|
|
736
|
+
`Cross-Origin-Resource-Policy`, and `Strict-Transport-Security` when
|
|
737
|
+
`PYWEB_COOKIE_SECURE` is on. Both servers now send the same set.
|
|
738
|
+
- `RedisCache` no longer unpickles data unless `allow_pickle=True`
|
|
739
|
+
(unpickling can run code written by anyone with Redis access).
|
|
740
|
+
|
|
741
|
+
### Fixed
|
|
742
|
+
- `@auth_required` server functions rejected sessions older than one
|
|
743
|
+
hour, while pages accepted them for 7 days.
|
|
744
|
+
|
|
745
|
+
## [0.4.3]
|
|
746
|
+
|
|
747
|
+
A review release: bug fixes, security hardening and speed-ups. No API
|
|
748
|
+
removals; one new setting (`PYWEB_TRUST_PROXY`).
|
|
749
|
+
|
|
750
|
+
### Fixed
|
|
751
|
+
- Pages showed stale data when a module-level value (`ITEMS = []`) was
|
|
752
|
+
changed by an `@server` function: the compiler copied its starting
|
|
753
|
+
value into browser code, and the page re-rendered with it. Values
|
|
754
|
+
that code changes now come from the server on each request.
|
|
755
|
+
- `db.transaction()`: a statement error that your code caught rolled
|
|
756
|
+
back the transaction's earlier writes while later ones still
|
|
757
|
+
committed. Errors inside a transaction now leave it to
|
|
758
|
+
`transaction()` (all or nothing).
|
|
759
|
+
- `db.stream()` inside a transaction waited for a second connection
|
|
760
|
+
(a 10 second hang with SQLite in memory); it now uses the
|
|
761
|
+
transaction's.
|
|
762
|
+
- `pyweb serve` sent SVG, images, fonts, JSON and WebAssembly as
|
|
763
|
+
`application/octet-stream` (browsers don't show an SVG sent that way).
|
|
764
|
+
- Your own files in `static/` (such as `app.css`) were cached by
|
|
765
|
+
browsers for a year, so deploys didn't show up for returning
|
|
766
|
+
visitors. They're now revalidated with an ETag (a `304` when
|
|
767
|
+
unchanged); content-hashed files, `?v=` URLs and npm packages are
|
|
768
|
+
still cached for a year.
|
|
769
|
+
- Client-side navigation could show a page prefetched before a server
|
|
770
|
+
call changed its data.
|
|
771
|
+
- `async def` server functions ignored `rpc_timeout`.
|
|
772
|
+
- The ASGI adapter kept only the last of repeated request headers, so
|
|
773
|
+
HTTP/2 clients sending each cookie separately lost all but one.
|
|
774
|
+
- `pyweb dts`: type aliases were never written, members on one line
|
|
775
|
+
(`{ a: string; b?: number }`) produced invalid Python, and optional
|
|
776
|
+
fields before required ones made the stub fail to import.
|
|
777
|
+
- Very long upload filenames are shortened (keeping the extension)
|
|
778
|
+
instead of failing to save.
|
|
779
|
+
|
|
780
|
+
### Security
|
|
781
|
+
- RPC rate limits used the `X-Forwarded-For` header, which any client
|
|
782
|
+
can set (so the limit was easy to get around), and put every visitor
|
|
783
|
+
without a session cookie in one shared bucket. They now use the
|
|
784
|
+
connection's address; behind a reverse proxy set `PYWEB_TRUST_PROXY=1`
|
|
785
|
+
(or the number of proxies) to use the address your proxy saw.
|
|
786
|
+
- `auth.require_session` and `auth.login_response` only redirect to
|
|
787
|
+
paths on your site (`//evil.example` becomes `/`); the new
|
|
788
|
+
`auth.safe_next()` does the same for your own `?next=` handling.
|
|
789
|
+
- Magic-link tokens are signed separately from session cookies, so one
|
|
790
|
+
can't be used as the other. Links issued before the upgrade stop
|
|
791
|
+
working (they last 15 minutes).
|
|
792
|
+
|
|
793
|
+
### Performance
|
|
794
|
+
- HTML, JavaScript, CSS, JSON and SVG are gzipped for browsers that
|
|
795
|
+
accept it (the shared runtime goes from 46 KB to 15 KB); static files
|
|
796
|
+
are compressed once and kept.
|
|
797
|
+
- Memory no longer grows without bound in long-running servers: the
|
|
798
|
+
rate limiter forgets idle callers, the realtime bus forgets channels
|
|
799
|
+
quiet for an hour, the in-memory cache drops expired entries, and
|
|
800
|
+
the job queue forgets jobs an hour after they finish.
|
|
801
|
+
- The job queue runs jobs on a pool of 8 threads instead of one thread
|
|
802
|
+
per job, and `wait()` returns as soon as the job finishes.
|
|
803
|
+
- `pyweb dev` no longer re-scans virtualenvs in the project folder
|
|
804
|
+
every 0.4 s, and reloads when `pyweb.lock` changes (after
|
|
805
|
+
`pyweb add`).
|
|
806
|
+
|
|
807
|
+
## [0.4.2]
|
|
808
|
+
|
|
809
|
+
### Added
|
|
810
|
+
- VS Code extension: **PyWeb: Run App** (a run button on `.pyweb`
|
|
811
|
+
files, Ctrl+F5), **New App** from any template, **Check App**, **Add
|
|
812
|
+
npm Package**, **Install or Update PyWeb** and **Set Up AI Assistant
|
|
813
|
+
(MCP)**, which adds the PyWeb MCP server to `.vscode/mcp.json`.
|
|
814
|
+
- VS Code extension: closing tags are added as you type, Emmet works in
|
|
815
|
+
markup, new snippets (`layout`, `error`, `stream`, `livequery`, `npm`,
|
|
816
|
+
`mount`), a status item that shows the PyWeb version in use, and an
|
|
817
|
+
icon.
|
|
818
|
+
|
|
819
|
+
### Fixed
|
|
820
|
+
- VS Code extension: the language server failed with
|
|
821
|
+
`spawn python ENOENT` when no Python interpreter was selected. The
|
|
822
|
+
extension now looks for a Python with PyWeb (the selected
|
|
823
|
+
interpreter, `pyweb`, `py`, `python3`, `python`), offers to install
|
|
824
|
+
PyWeb when it's missing, and restarts when you change interpreter.
|
|
825
|
+
|
|
826
|
+
## [0.4.1]
|
|
827
|
+
|
|
828
|
+
### Added
|
|
829
|
+
- MCP server: `pyweb_routes` maps every page (route, path and query
|
|
830
|
+
parameters with types and defaults, title and head tags, layouts,
|
|
831
|
+
live-data variables), the layouts, error pages and server functions.
|
|
832
|
+
- MCP server: `pyweb_packages` adds, removes or lists npm packages for
|
|
833
|
+
browser code, with each package's exported names and the `npm(...)`
|
|
834
|
+
lines to bind them.
|
|
835
|
+
- MCP server: `add_ai_feature` and `make_data_live` prompts.
|
|
836
|
+
- MCP server: `pyweb_call` returns every chunk a streaming function
|
|
837
|
+
yields; `pyweb_render` reports the title, head tags and the layouts that
|
|
838
|
+
rendered; `pyweb_check` reports layouts, live variables, npm packages
|
|
839
|
+
and streaming functions.
|
|
840
|
+
- The AI guide (`pyweb_guide`, `AGENTS.md`) has sections with working
|
|
841
|
+
examples for multi-page apps, streaming and AI, live data and npm
|
|
842
|
+
packages.
|
|
843
|
+
- A new README with screenshots, and docs on publishing the VS Code
|
|
844
|
+
extension.
|
|
845
|
+
|
|
846
|
+
### Fixed
|
|
847
|
+
- Markup right after a `def` line that ends in a comment
|
|
848
|
+
(`def Page(q: str = ""): # ...`) failed to compile.
|
|
849
|
+
|
|
850
|
+
## [0.4.0]
|
|
851
|
+
|
|
852
|
+
Apps that are bigger, livelier and smarter: npm packages without
|
|
853
|
+
Node.js, multi-page apps with layouts and client-side navigation,
|
|
854
|
+
streaming for AI features, and page data that follows the database.
|
|
855
|
+
|
|
856
|
+
### Added
|
|
857
|
+
- **npm packages without Node.js.** `pyweb add chart.js/auto` downloads a
|
|
858
|
+
package from the npm registry, checks its sha512 checksum, follows its
|
|
859
|
+
imports from the browser entry point and copies only the files it
|
|
860
|
+
needs (and its dependencies) into `static/vendor/`, pinning versions in
|
|
861
|
+
`pyweb.lock`. `pyweb add` with no arguments reinstalls from the lock;
|
|
862
|
+
`pyweb remove` takes a package out. Browser code binds exports with
|
|
863
|
+
`Chart = npm("chart.js/auto")` or `npm("pkg", "Export")`. Calling a
|
|
864
|
+
class constructs it and keyword arguments become an options object.
|
|
865
|
+
Each page imports only the packages it uses, through an import map
|
|
866
|
+
that the Content Security Policy allows by hash. CommonJS-only and
|
|
867
|
+
Node.js-only packages are refused with an explanation, and an
|
|
868
|
+
uninstalled package is a compile error that names the command to run.
|
|
869
|
+
- **`ref={el}`** sets a page variable to an element, in time for
|
|
870
|
+
`on_mount`, for libraries that draw into the page.
|
|
871
|
+
- Changing an attribute of a package's object (`chart.value = 5`)
|
|
872
|
+
re-renders whatever shows it.
|
|
873
|
+
- Web components (custom elements from npm) work in markup.
|
|
874
|
+
- `pyweb lsp`: hover shows an npm binding's installed version and
|
|
875
|
+
TypeScript signature; completion offers installed packages in
|
|
876
|
+
`npm("` and a module's exports after `name.`.
|
|
877
|
+
- New docs page: npm packages.
|
|
878
|
+
- **Layouts.** `@app.layout` wraps every page (or, with
|
|
879
|
+
`@app.layout("/admin")`, pages under a prefix) in shared markup;
|
|
880
|
+
`{children}` marks where the page goes. Layouts nest, have their own
|
|
881
|
+
server code, state and handlers, and pages can opt out with
|
|
882
|
+
`layout=None` or pick one with `layout="Name"`.
|
|
883
|
+
- **Client-side navigation.** Links between pages fetch the next page's
|
|
884
|
+
HTML and swap it in below the layouts both pages share, so layout
|
|
885
|
+
state (an open menu, a player) survives. Links are prefetched on
|
|
886
|
+
hover or focus, back/forward restore the scroll position, and anything
|
|
887
|
+
unusual falls back to a full page load. `navigate("/path")` from
|
|
888
|
+
`pyweb.browser` does the same from browser code; `on_unmount` runs
|
|
889
|
+
when a page is left; `App(client_nav=False)` turns it off.
|
|
890
|
+
- **Current links.** Links to the current page get
|
|
891
|
+
`aria-current="page"` and links to its parent sections
|
|
892
|
+
`aria-current="true"`, on the server and after each navigation.
|
|
893
|
+
- **Head tags.** `@app.page(description=..., image=..., noindex=...)` and
|
|
894
|
+
`head(title=..., description=...)` in page code add description, Open
|
|
895
|
+
Graph and Twitter tags; `App(base_url=...)` adds canonical URLs and
|
|
896
|
+
absolute image links.
|
|
897
|
+
- **Error pages in `.pyweb`.** `@app.error(404)` / `@app.error(500)` pages
|
|
898
|
+
take `path`, `status`, `message` or `request_id` and use root layouts.
|
|
899
|
+
- **Typed query parameters.** Page parameters that aren't in the route
|
|
900
|
+
are read from the query string and converted by annotation (`int`,
|
|
901
|
+
`float`, `bool`, `list[...]`); bad or missing values answer 400.
|
|
902
|
+
- New docs page: Layouts & navigation.
|
|
903
|
+
- **Streaming server functions.** A `@server` function that `yield`s
|
|
904
|
+
sends each value as it's produced (NDJSON over the same RPC endpoint);
|
|
905
|
+
browser code reads it with `async for`. `stream.cancel()` or leaving the
|
|
906
|
+
loop aborts the request and closes the generator on the server, so an
|
|
907
|
+
upstream AI call stops too. Errors raised part-way arrive as
|
|
908
|
+
`RPCError`. Works with `async def` generators, under `pyweb serve`,
|
|
909
|
+
`pyweb dev` and ASGI, and `TestClient.rpc` returns the streamed values.
|
|
910
|
+
- **`<Markdown text={...} />`** (from `pyweb`) renders Markdown to safe
|
|
911
|
+
HTML on the server and updates it in the browser as the text changes,
|
|
912
|
+
with the same rules in both places. Raw HTML is shown as text and
|
|
913
|
+
links only accept http(s), mailto and relative URLs.
|
|
914
|
+
- **`ai-chat` template and example**: a streaming chat with Stop and
|
|
915
|
+
Markdown replies that talks to Anthropic, any OpenAI-compatible server
|
|
916
|
+
(OpenAI, Ollama, vLLM, ...) or a built-in demo model, chosen by
|
|
917
|
+
environment variables. `pyweb new NAME --template ai-chat`.
|
|
918
|
+
- New docs page: Building AI apps.
|
|
919
|
+
- **Live data.** `rows = live(db, "select ...", params)` in a page or
|
|
920
|
+
layout renders the rows and keeps them current: writes through
|
|
921
|
+
`pyweb.db` (and `pyweb.models`) announce their table after commit, each
|
|
922
|
+
distinct query re-runs once per change (bursts coalesced) and sends
|
|
923
|
+
rows to every page showing it only when they changed. Pages follow a
|
|
924
|
+
signed feed; `db.notify("table")` covers writes made elsewhere. With
|
|
925
|
+
`RedisBus` it works across processes, and any process can take over a
|
|
926
|
+
query from the page's signed description.
|
|
927
|
+
- New docs page: Live data.
|
|
928
|
+
- **`watch(lambda: value, handler)`** (from `pyweb.browser`) runs a
|
|
929
|
+
handler whenever a value changes, for work outside markup such as
|
|
930
|
+
redrawing a chart or saving a draft.
|
|
931
|
+
- `pyweb add` applies packages' `browser` field (browser versions of
|
|
932
|
+
files, modules turned off), so packages such as ethers install.
|
|
933
|
+
- New examples: **dashboard** (live queries and a Chart.js chart from
|
|
934
|
+
npm), **site** (layouts, navigation, query parameters, a 404 page) and
|
|
935
|
+
**ai-chat**. The playground has the site and AI chat examples.
|
|
936
|
+
- New docs page: Recipe: wallets & web3 (wallet sign-in with ethers and
|
|
937
|
+
eth-account).
|
|
938
|
+
- A layout's `{children}` can sit on a line of its own.
|
|
939
|
+
|
|
940
|
+
### Fixed
|
|
941
|
+
- Subscriptions and cleanups set up in `on_mount` now end when the page
|
|
942
|
+
is left (they were never stopped).
|
|
943
|
+
|
|
944
|
+
### Changed
|
|
945
|
+
- The experimental `pyweb.live` module (in-memory live tables) is now
|
|
946
|
+
`pyweb.livetable`; `pyweb.live` is the new live-query function.
|
|
947
|
+
- `pyweb.npm` (TypeScript declarations to dataclasses) is now
|
|
948
|
+
`pyweb.dts`, and its command is `pyweb dts`. `pyweb build` no longer
|
|
949
|
+
writes an esm.sh import map; packages come from `pyweb.lock`.
|
|
950
|
+
- The default Content Security Policy adds `worker-src 'self' blob:` so
|
|
951
|
+
packages can start Web Workers.
|
|
952
|
+
- The starter stylesheet styles Markdown and chat bubbles.
|
|
953
|
+
- The browser runtime is about 14 KB gzipped (was about 11 KB), for
|
|
954
|
+
client-side navigation and npm support.
|
|
955
|
+
- New website design: a colour system where blue means the browser and
|
|
956
|
+
amber the server, self-hosted Inter / Bricolage Grotesque / JetBrains
|
|
957
|
+
Mono, dark code windows, and a landing page that shows each line of an
|
|
958
|
+
app with where it runs (worked out by the compiler) next to the real
|
|
959
|
+
compiled output.
|
|
960
|
+
- The example apps and the `pyweb new` stylesheet use the new palette.
|
|
961
|
+
|
|
962
|
+
## [0.3.0]
|
|
963
|
+
|
|
964
|
+
Faster pages that feel like an app, apps that span several files, and
|
|
965
|
+
tools for editors, the browser and AI assistants.
|
|
966
|
+
|
|
967
|
+
### Added
|
|
968
|
+
- **Hydration.** Pages now adopt the server-rendered DOM instead of
|
|
969
|
+
rebuilding it: nodes are kept, focus and text typed before the script
|
|
970
|
+
loads survive, and typed values reach their variables. When the server
|
|
971
|
+
HTML doesn't match, the page falls back to a client render and logs
|
|
972
|
+
a warning. The page root carries `data-pw-mode="hydrated"` or
|
|
973
|
+
`"rendered"`.
|
|
974
|
+
- **Live updates.** `publish(name, data)` in server code pushes to every
|
|
975
|
+
page that called `subscribe(channel_feed, handler)`, over Server-Sent
|
|
976
|
+
Events with a polling fallback. Feeds come from `channel(name)` while
|
|
977
|
+
rendering: signed, expiring, and positioned so no message published
|
|
978
|
+
after the render is missed. `pyweb dev`, `pyweb serve` and the ASGI
|
|
979
|
+
adapter stream; `realtime.use_bus()` shares the bus (e.g. Redis)
|
|
980
|
+
between processes. The chat example uses it instead of polling.
|
|
981
|
+
- **Multi-file apps.** `from widgets import Card, save, COLOR` imports
|
|
982
|
+
components, `@server` functions and constants from `widgets.pyweb` (or
|
|
983
|
+
`ui/cards.pyweb` as `ui.cards`). Each file keeps its own names in the
|
|
984
|
+
generated JavaScript, imported files run as real modules on the
|
|
985
|
+
server, `pyweb build` copies them into `dist/`, and errors name the
|
|
986
|
+
file they're in. Pages stay in the app file; circular imports and
|
|
987
|
+
duplicate server-function names are compile errors.
|
|
988
|
+
- **Editor support.** `pyweb lsp` is a language server (stdio, standard
|
|
989
|
+
library only): compile errors and security warnings as you type,
|
|
990
|
+
hover showing where each name runs and why, completion for components,
|
|
991
|
+
props and tags, go to definition across files, and an outline. The new
|
|
992
|
+
VS Code extension (`editors/vscode`) adds highlighting and snippets and
|
|
993
|
+
starts it; releases attach a `.vsix`. Setup for Neovim and Helix is in
|
|
994
|
+
the command-line docs.
|
|
995
|
+
- **Playground** on the website: edit an app and run it in the browser,
|
|
996
|
+
with the real compiler, server rendering, `@server` functions,
|
|
997
|
+
sessions and live updates running on Pyodide. Shows the compiled
|
|
998
|
+
JavaScript and what runs where, reports errors with a fix, and shares
|
|
999
|
+
code as links.
|
|
1000
|
+
- **MCP tools** `pyweb_screenshot` (open a page in headless Chromium,
|
|
1001
|
+
run click/fill/press steps, get a screenshot, the page text, console
|
|
1002
|
+
errors and hydration status) and `pyweb_test` (run the app's pytest
|
|
1003
|
+
tests and report failures). New apps include a starter `test_app.py`.
|
|
1004
|
+
- `python -m pyweb.bench --max-ssr-ms N --max-compile-ms N` fails when
|
|
1005
|
+
a benchmark exceeds its budget; CI runs it.
|
|
1006
|
+
|
|
1007
|
+
### Removed
|
|
1008
|
+
- The old `pyweb.lsp` helper functions (`complete`, `DocumentState`,
|
|
1009
|
+
`complete_*`, `diagnostics_for_compile_error`, `hover(compiled, name)`,
|
|
1010
|
+
`boundary_lens`); they described an earlier API and no editor used them.
|
|
1011
|
+
|
|
1012
|
+
### Changed
|
|
1013
|
+
- `import pyweb` no longer needs the `sqlite3` module (it's imported when
|
|
1014
|
+
a SQLite database is opened), and password hashing falls back to a
|
|
1015
|
+
pure-Python PBKDF2 on Pythons without OpenSSL. Both make PyWeb run on
|
|
1016
|
+
Pyodide.
|
|
1017
|
+
- `/__pyweb/events` and `/__pyweb/poll` require a signed `?feed=`; the
|
|
1018
|
+
unauthenticated `?channel=` form is gone, so channels are no longer
|
|
1019
|
+
readable by anyone who guesses a name.
|
|
1020
|
+
- Server rendering caches compiled template expressions; rendering a
|
|
1021
|
+
page is about twice as fast.
|
|
1022
|
+
- Generated page modules pass element children as a function and wrap
|
|
1023
|
+
every child in a call (`$t`, `$dyn`), so nodes are created in document
|
|
1024
|
+
order.
|
|
1025
|
+
|
|
1026
|
+
## [0.2.0]
|
|
1027
|
+
|
|
1028
|
+
Tools for building PyWeb apps with AI assistants.
|
|
1029
|
+
|
|
1030
|
+
### Added
|
|
1031
|
+
- `pyweb mcp`: a Model Context Protocol server (stdio, standard library
|
|
1032
|
+
only) for Claude Code, Cursor, Claude Desktop, VS Code and other MCP
|
|
1033
|
+
clients. Tools: `pyweb_guide`, `pyweb_new_app`, `pyweb_check` (errors
|
|
1034
|
+
with line numbers and fix hints), `pyweb_inspect`, `pyweb_compiled`,
|
|
1035
|
+
`pyweb_render` and `pyweb_call` (session cookies persist between calls);
|
|
1036
|
+
resources for the guide and templates; a `build_pyweb_app` prompt.
|
|
1037
|
+
Tested against the official MCP Python SDK.
|
|
1038
|
+
- An AI-oriented guide to writing `.pyweb` apps, shipped in the package.
|
|
1039
|
+
- `pyweb new --template blank|counter|todo|blog|auth|chat`; new projects
|
|
1040
|
+
include `AGENTS.md` and `CLAUDE.md` for coding agents.
|
|
1041
|
+
- The docs site publishes `llms.txt` and `llms-full.txt`, and has a new
|
|
1042
|
+
"AI assistants & MCP" page.
|
|
1043
|
+
|
|
1044
|
+
## [0.1.0]
|
|
1045
|
+
|
|
1046
|
+
The first public release, published on PyPI as `pyweb-stack`. The
|
|
1047
|
+
unpublished prototype was labelled "1.0" but only the simplest counter
|
|
1048
|
+
pattern worked in a browser; this release rebuilds the compiler, runtime
|
|
1049
|
+
and server around a design that works end to end, and is verified in a
|
|
1050
|
+
real browser.
|
|
1051
|
+
|
|
1052
|
+
### Language and compiler
|
|
1053
|
+
- `.pyweb` parser with a character scanner: tags and expressions may span
|
|
1054
|
+
lines, braces and strings nest inside `{...}`, `elif` chains, HTML
|
|
1055
|
+
comments, and exact line numbers (the Python half keeps the source's
|
|
1056
|
+
line count; this also fixes crashes on Python 3.10/3.11).
|
|
1057
|
+
- Python → JavaScript translation of a documented subset with Python
|
|
1058
|
+
semantics (truthiness, `==` on containers, negative indexing, floor
|
|
1059
|
+
division/modulo, string/list/dict/set methods, Python exceptions).
|
|
1060
|
+
Unsupported code is a compile error with `file:line`. Verified
|
|
1061
|
+
differentially against CPython.
|
|
1062
|
+
- State inference: signals (mutated by handlers or bound), computeds,
|
|
1063
|
+
constants; initial values from literals, the browser, or the server.
|
|
1064
|
+
Mutations (`append`, item assignment, `del`, …) are copy-on-write.
|
|
1065
|
+
- Components with props, defaults and `children`; `on_mount` hook;
|
|
1066
|
+
page titles; `App(title=, stylesheets=, lang=)`.
|
|
1067
|
+
- `@server` calls from handlers compile to awaited typed RPC; async
|
|
1068
|
+
propagates through handler calls.
|
|
1069
|
+
- Only values browser code reads are serialised; values derived from
|
|
1070
|
+
server data are computed on the server. Secret-looking names read by
|
|
1071
|
+
browser code are rejected.
|
|
1072
|
+
|
|
1073
|
+
### Runtime
|
|
1074
|
+
- New browser runtime: dependency-tracked signals, cached computeds,
|
|
1075
|
+
owned effects with disposal, batching; keyed lists and conditionals in
|
|
1076
|
+
marker-bounded regions; two-way binding for text, number, checkbox,
|
|
1077
|
+
radio and select; `javascript:` URL blocking; RPC client with typed
|
|
1078
|
+
errors.
|
|
1079
|
+
|
|
1080
|
+
### Server
|
|
1081
|
+
- Pages render per request with real server values; `request`,
|
|
1082
|
+
`session`, `redirect`, `NotFound`; typed route parameters.
|
|
1083
|
+
- `@server` functions are registered automatically in `dev`, `serve`,
|
|
1084
|
+
tests and ASGI.
|
|
1085
|
+
- New ASGI adapter: `pyweb.asgi.create_app`.
|
|
1086
|
+
- RPC: JSON-only and same-origin checks, rate limiting, timeouts that
|
|
1087
|
+
actually return on time, async functions, JSON conversion for
|
|
1088
|
+
dataclasses/datetimes/Decimals.
|
|
1089
|
+
- Content-Security-Policy and security headers on HTML responses.
|
|
1090
|
+
|
|
1091
|
+
### Tooling
|
|
1092
|
+
- `pyweb dev`: live reload and an in-browser compile-error overlay.
|
|
1093
|
+
- `pyweb build` produces a self-contained `dist/` (source, manifest with
|
|
1094
|
+
gzip sizes, hashed assets, working Dockerfile); `pyweb serve` runs it.
|
|
1095
|
+
- Token-aware JS minifier (the previous one altered string literals).
|
|
1096
|
+
- `pyweb.testing.TestClient` and `pyweb.testing.serve` for tests.
|
|
1097
|
+
- Clean `file:line: message` compile errors from the CLI.
|
|
1098
|
+
|
|
1099
|
+
### Data, auth, jobs
|
|
1100
|
+
- `?` placeholders work on Postgres and MySQL; lazy connection pools.
|
|
1101
|
+
- WebAuthn: challenge, origin and sign-count checks; DER signatures; uses
|
|
1102
|
+
`cryptography` when installed.
|
|
1103
|
+
- Redis bus and queue work against real Redis.
|
|
1104
|
+
|
|
1105
|
+
### Fixed
|
|
1106
|
+
- Migrations: the `pyweb_migrations` journal table can now be created on
|
|
1107
|
+
MySQL (`VARCHAR(255)` key instead of `TEXT`).
|
|
1108
|
+
- `pyweb serve` crashed on startup (logger misconfiguration).
|
|
1109
|
+
- Production builds referenced a hashed runtime the page modules never
|
|
1110
|
+
imported.
|
|
1111
|
+
- Installed packages were missing the browser runtime file.
|
|
1112
|
+
- `transaction()` did not roll back on Postgres/MySQL pools.
|
|
1113
|
+
- `sqlite:///file.db` pointed at the filesystem root.
|
|
1114
|
+
- `RedisBus.since()` duplicated and mis-ordered messages;
|
|
1115
|
+
`RedisQueue.drain(timeout=0)` blocked forever.
|
|
1116
|
+
- Jobs raising `TypeError` ran twice.
|
|
1117
|
+
- `Query.order_by()` ignored `-field` and did not validate a single field.
|
|
1118
|
+
- The deploy Dockerfile ran a non-existent `serve --dir` flag.
|
|
1119
|
+
|
|
1120
|
+
### Removed
|
|
1121
|
+
- Modules the framework no longer uses: `pyweb.components` (pure-Python
|
|
1122
|
+
element API), `pyweb.routing` (unused router), `pyweb.reactive`
|
|
1123
|
+
(server-side `Signal`/`Computed`/`Effect`/`live`), and
|
|
1124
|
+
`pyweb.compiler.placement` (superseded by the compiler's boundary checks).
|
|
1125
|
+
- The `@browser` and `@shared` decorators, which had no effect.
|
|
1126
|
+
- `pyweb.compiler.rpc.client_stub`/`server_handler` and unused AST classes.
|
|
1127
|
+
- `docs/BUGLOG.md` (it described the prototype's internals) and the committed
|
|
1128
|
+
`website/dist/`; the Pages workflow builds the site.
|
|
1129
|
+
- The regex-based codegen, the virtual test client that only recorded
|
|
1130
|
+
clicks, documentation "snippets" that did not use PyWeb, island and
|
|
1131
|
+
streaming-SSR string helpers without runtime support, and the no-op
|
|
1132
|
+
`--security-scan` flag.
|
|
1133
|
+
|
|
1134
|
+
## Pre-release prototype (unpublished)
|
|
1135
|
+
- Initial prototype.
|