pyweb-stack 0.6.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.0 → pyweb_stack-0.6.1}/CHANGELOG.md +53 -0
- {pyweb_stack-0.6.0/pyweb_stack.egg-info → pyweb_stack-0.6.1}/PKG-INFO +3 -3
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/README.md +2 -2
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/02-quickstart.md +6 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/14-cli.md +35 -2
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/16-limitations-roadmap.md +8 -4
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/17-ai-assistants.md +2 -1
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/27-upgrading.md +1 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/29-performance.md +11 -1
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyproject.toml +2 -2
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/app_loader.py +26 -7
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/bench.py +36 -1
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/cli/__init__.py +79 -32
- pyweb_stack-0.6.1/pyweb/compiler/errors.py +223 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/lower.py +10 -1
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/pipeline.py +51 -1
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/pyjs.py +17 -3
- pyweb_stack-0.6.1/pyweb/doctor.py +303 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/lsp_ui.py +19 -2
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/mcp.py +12 -49
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/mcp_tools.py +22 -0
- pyweb_stack-0.6.1/pyweb/runtime/browser/dev/reload.js +102 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/runtime.js +2 -1
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/server/__init__.py +2 -2
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/extending.py +7 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/upgrade.py +12 -1
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1/pyweb_stack.egg-info}/PKG-INFO +3 -3
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/SOURCES.txt +7 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_auth.py +2 -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_dev_reload.py +53 -0
- pyweb_stack-0.6.1/tests/test_doctor.py +128 -0
- pyweb_stack-0.6.1/tests/test_error_messages.py +164 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_forms_v5.py +2 -1
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_mcp.py +3 -1
- pyweb_stack-0.6.0/pyweb/compiler/errors.py +0 -29
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/LICENSE +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/MANIFEST.in +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/THREAT_MODEL.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/01-introduction.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/03-tutorial.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/04-pyweb-files.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/05-reactivity.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/06-server-functions.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/07-browser-python.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/08-pages-routing-assets.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/09-data.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/10-auth.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/11-testing.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/12-deployment.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/13-security.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/15-toolkit.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/18-npm-packages.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/19-layouts-navigation.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/20-ai-apps.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/21-live-data.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/22-recipe-web3.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/23-forms.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/24-jobs.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/25-scaling.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/26-observability.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/28-saas-tutorial.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/30-teams.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/31-pwa-push.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/32-desktop.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/33-webhooks-files.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/34-ui-and-motion.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/__init__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/_speedups.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/_templates.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/a11y.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ai/guide.md +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/app.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/asgi.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/auth.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/__init__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/admin.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/html.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/models.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/oauth.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/orgpages.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/orgs.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/pages.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/passwords.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/privacy.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/teams.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/throttle.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/tokens.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/webauthn.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/browser.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/build.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/cache.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/cli/__main__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/__init__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/ast.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/codegen/__init__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/codegen/ir.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/parser.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/rpc.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/config.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/context.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/csp_report.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/css.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/db/__init__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/db/dialect.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/db/migrate.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/db/rls.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/db/schema.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/db/seeds.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/decorators.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/deploy/__init__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/deploy/legacy.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/deploy/plan.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/deploy/targets.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/deprecation.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/desktop/__init__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/desktop/gate.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/desktop/package.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/desktop/shell.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/devlink.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/dts.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/fieldcrypt.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/filegc.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/forms.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/hosting.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/http.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/__init__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/builtin.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/core.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/db.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/legacy.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/memory.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/redis.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/schedule.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/worker.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/keys.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/livedata.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/livetable.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/lsp.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/lsp_data.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/lsp_teams.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/mail.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/markdown.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/mcp_http.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/models/__init__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/models/fields.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/models/query.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/models/relations.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/motion.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/net/__init__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/net/http.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/net/live.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/net/server.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/net/ws.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/observability.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/openapi.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/packages.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/platform.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/plugins.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/profile.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/push.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/pwa.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/py.typed +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/realtime.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/rooms.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/rpc.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/rules.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/auth.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/desktop.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/forms.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/live.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/markdown.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/motion-api.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/motion-count.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/motion-drag.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/motion-layout.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/motion-math.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/motion.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/pwa.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/sw.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/accordion.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/calendar.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/carousel.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/checkbox.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/collapsible.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/combobox.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/command.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/context-menu.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/core.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/dialog.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/hover-card.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/index.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/listbox.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/menu.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/menubar.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/navigation-menu.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/otp.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/popover.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/radio-group.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/resizable.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/select.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/sidebar.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/slider.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/tabs.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/toaster.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/toggle-group.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/toggle.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/tooltip.js +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/server/argcheck.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/sbom.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/secret_scan.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/security.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/serve.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ssr.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ssr_codegen.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/storage.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/sync.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/telemetry/__init__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/telemetry/devtools.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/telemetry/instruments.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/telemetry/logs.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/telemetry/metrics.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/telemetry/tracing.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/ai-chat.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/app.css +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/auth.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/blog.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/chat.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/counter.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/desktop/app.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/desktop/test_app.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/saas/app.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/saas/seeds.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/saas/test_app.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/todo.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/tenancy.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/testing.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/__init__.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/cli.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/button.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/card.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/carousel.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/chart.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/command.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/controls.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/data.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/date.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/dialog.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/disclosure.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/display.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/layout.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/menu.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/overlay.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/style.pyweb +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/serve.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/source_edit.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/static/ui.css +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/theme.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/uploads.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/webhooks.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/dependency_links.txt +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/entry_points.txt +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/requires.txt +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/top_level.txt +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/setup.cfg +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/setup.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_account_privacy.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_api_tokens.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_auth_contract.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_auth_security.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_auth_sessions.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_auth_v1.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_authkit.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_authkit_units.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_backplane.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_browser_build.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_budget.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_cli_deploy.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_codegen.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_compiler.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_cron.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_customise.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_data_layer.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_db_production.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_deploy_upload_render.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_deploy_v5.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_desktop.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_docs.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_dts.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_dx_v5.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_e2e.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_encrypted_fields.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_examples.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_filegc.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_forms_security_obs.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_fuzz_requests.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_http_fetch.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_jobs_app.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_jobs_cache_realtime.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_jobs_v5.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_keys_sessions.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_live_patches.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_livedata.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_lsp.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_lsp_teams.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_markdown.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_mcp_teams.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_mcp_v6.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_migrations_property.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_migrations_v5.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_models_db.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_motion.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_multifile.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_multipage.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_net.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_observability.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_openapi.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_org_pages.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_packages.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_parser.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_parser_v1.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_plugins_platform_bench.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_production_gaps.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_profile.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_push.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_pwa.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_pyjs_semantics.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_rate_per_function.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_reactivity.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_realtime_transport.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_reference_apps.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_request_hardening.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_rpc_placement.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_rpc_production.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_runtime_signals.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_sbom.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_secret_scan.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_server_db.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_speedups.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_ssr_codegen.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_static_hashing.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_static_serving.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_streaming.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_sync_live.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_systems.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_telemetry.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_tenancy.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_testing_orgs.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_threat_model.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_trusted_types.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_ui_kit.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_webhooks.py +0 -0
- {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_website.py +0 -0
|
@@ -6,6 +6,59 @@ project uses [semantic versioning](https://semver.org).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
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
|
+
|
|
9
62
|
## [0.6.0]
|
|
10
63
|
|
|
11
64
|
Teams, installable and desktop apps, a UI kit and motion, and security by
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyweb-stack
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Full-stack web apps in one Python file: server-rendered pages, reactive browser UI, typed RPC.
|
|
5
5
|
Author-email: MaanavKrishna <67054795+MaanavKrishna@users.noreply.github.com>
|
|
6
6
|
Maintainer-email: MaanavKrishna <67054795+MaanavKrishna@users.noreply.github.com>
|
|
@@ -478,7 +478,7 @@ The [architecture overview](ARCHITECTURE.md) goes through each stage.
|
|
|
478
478
|
|
|
479
479
|
## Build it with AI
|
|
480
480
|
|
|
481
|
-
PyWeb ships an MCP server with
|
|
481
|
+
PyWeb ships an MCP server with 30 tools, so AI assistants can do what
|
|
482
482
|
you'd do: scaffold an app, search the docs, check each edit (errors come
|
|
483
483
|
back as line numbers with fix hints), build with the UI kit and a theme,
|
|
484
484
|
render pages and call server functions as any signed-in member, look at
|
|
@@ -566,7 +566,7 @@ Upgrade with `pip install -U pyweb-stack`.
|
|
|
566
566
|
|
|
567
567
|
| Version | Highlights |
|
|
568
568
|
|---|---|
|
|
569
|
-
| 0.6 | Teams with roles, invitations and tenant-scoped Models (Postgres row-level security), API tokens and OpenAPI, webhooks, installable apps with Web Push, desktop windows and installers, a UI kit with themes and motion, Trusted Types, encrypted fields and a threat model, about 2.5× faster serving, Python 3.15 and free-threaded builds, a much richer MCP server |
|
|
569
|
+
| 0.6 | Teams with roles, invitations and tenant-scoped Models (Postgres row-level security), API tokens and OpenAPI, webhooks, installable apps with Web Push, desktop windows and installers, a UI kit with themes and motion, Trusted Types, encrypted fields and a threat model, about 2.5× faster serving, Python 3.15 and free-threaded builds, a much richer MCP server; 0.6.1: error messages with code frames and suggestions, `pyweb doctor`, dev reloads that keep page state, 4× faster reloads and tests |
|
|
570
570
|
| 0.5 | Models with migrations, forms, auth kit with passkeys, OAuth and an admin, row-level live updates over WebSockets on PyWeb's own server, durable jobs and schedules, `pyweb deploy` for docker, compose, k8s, fly, render and railway, logs, metrics, tracing and a dev toolbar, data-aware editor and MCP tools, the `saas` template, `pyweb upgrade` |
|
|
571
571
|
| 0.4 | npm packages without Node, layouts and client-side navigation, streaming server functions and `<Markdown>` for AI apps, live queries, page head tags, `.pyweb` error pages; 0.4.1: richer MCP tools; 0.4.2: VS Code run, new-app and MCP commands; 0.4.3: fixes, security hardening, gzip and caching; 0.4.4: key rotation, session revocation, typed arguments, Redis-shared limits, `check --production` |
|
|
572
572
|
| 0.3 | Hydration, live updates (SSE), multi-file apps, language server + VS Code extension, browser playground, faster rendering, screenshot/test MCP tools |
|
|
@@ -404,7 +404,7 @@ The [architecture overview](ARCHITECTURE.md) goes through each stage.
|
|
|
404
404
|
|
|
405
405
|
## Build it with AI
|
|
406
406
|
|
|
407
|
-
PyWeb ships an MCP server with
|
|
407
|
+
PyWeb ships an MCP server with 30 tools, so AI assistants can do what
|
|
408
408
|
you'd do: scaffold an app, search the docs, check each edit (errors come
|
|
409
409
|
back as line numbers with fix hints), build with the UI kit and a theme,
|
|
410
410
|
render pages and call server functions as any signed-in member, look at
|
|
@@ -492,7 +492,7 @@ Upgrade with `pip install -U pyweb-stack`.
|
|
|
492
492
|
|
|
493
493
|
| Version | Highlights |
|
|
494
494
|
|---|---|
|
|
495
|
-
| 0.6 | Teams with roles, invitations and tenant-scoped Models (Postgres row-level security), API tokens and OpenAPI, webhooks, installable apps with Web Push, desktop windows and installers, a UI kit with themes and motion, Trusted Types, encrypted fields and a threat model, about 2.5× faster serving, Python 3.15 and free-threaded builds, a much richer MCP server |
|
|
495
|
+
| 0.6 | Teams with roles, invitations and tenant-scoped Models (Postgres row-level security), API tokens and OpenAPI, webhooks, installable apps with Web Push, desktop windows and installers, a UI kit with themes and motion, Trusted Types, encrypted fields and a threat model, about 2.5× faster serving, Python 3.15 and free-threaded builds, a much richer MCP server; 0.6.1: error messages with code frames and suggestions, `pyweb doctor`, dev reloads that keep page state, 4× faster reloads and tests |
|
|
496
496
|
| 0.5 | Models with migrations, forms, auth kit with passkeys, OAuth and an admin, row-level live updates over WebSockets on PyWeb's own server, durable jobs and schedules, `pyweb deploy` for docker, compose, k8s, fly, render and railway, logs, metrics, tracing and a dev toolbar, data-aware editor and MCP tools, the `saas` template, `pyweb upgrade` |
|
|
497
497
|
| 0.4 | npm packages without Node, layouts and client-side navigation, streaming server functions and `<Markdown>` for AI apps, live queries, page head tags, `.pyweb` error pages; 0.4.1: richer MCP tools; 0.4.2: VS Code run, new-app and MCP commands; 0.4.3: fixes, security hardening, gzip and caching; 0.4.4: key rotation, session revocation, typed arguments, Redis-shared limits, `check --production` |
|
|
498
498
|
| 0.3 | Hydration, live updates (SSE), multi-file apps, language server + VS Code extension, browser playground, faster rendering, screenshot/test MCP tools |
|
|
@@ -88,6 +88,12 @@ pyweb serve dist # production server with /healthz
|
|
|
88
88
|
`dist/` contains your app source, static assets and a `Dockerfile`; see
|
|
89
89
|
[Deployment](12-deployment.md).
|
|
90
90
|
|
|
91
|
+
If something doesn't work and the error isn't clear, `pyweb doctor`
|
|
92
|
+
checks the project and your machine (Python, the app, optional packages,
|
|
93
|
+
the database and migrations, the port, your editor) and prints a fix for
|
|
94
|
+
each problem; `pyweb doctor --production` also checks the production
|
|
95
|
+
settings.
|
|
96
|
+
|
|
91
97
|
## Next
|
|
92
98
|
|
|
93
99
|
The [tutorial](03-tutorial.md) builds a notes app with a database,
|
|
@@ -5,13 +5,20 @@ pyweb new NAME [--template T] create NAME/ with app.pyweb, test_app.py, A
|
|
|
5
5
|
T = blank | counter (default) | todo | blog | saas | auth | chat | ai-chat | desktop
|
|
6
6
|
(apps with a database also get migrations/0001_initial.py and a README)
|
|
7
7
|
pyweb dev FILE [--port 8000] [--host 127.0.0.1] [--no-reload]
|
|
8
|
-
development server: recompile on save, live reload
|
|
8
|
+
development server: recompile on save, live reload that keeps the
|
|
9
|
+
page's variables, form fields, scroll and focus (add ?fresh to
|
|
10
|
+
the URL to start over), error overlay,
|
|
9
11
|
the dev toolbar (requests, SQL, jobs, emails, theme and motion
|
|
10
12
|
editors), and background jobs in the same process; writes
|
|
11
13
|
.pyweb/dev.json so `pyweb mcp` can see its errors and logs
|
|
12
14
|
pyweb check FILE [--production] [--strict]
|
|
13
15
|
compile, security and accessibility checks; --production also checks
|
|
14
16
|
the environment; --strict fails on warnings too
|
|
17
|
+
pyweb doctor [FILE] [--production] [--json] [--port 8000]
|
|
18
|
+
is the project ready? Python, the app compiles, optional packages,
|
|
19
|
+
database and migrations, Redis, secrets in the source, upgrade
|
|
20
|
+
changes, a stale dev server link, tests, the port, the editor; a fix
|
|
21
|
+
for each problem (exit 1 on any failure; --json for CI)
|
|
15
22
|
pyweb upgrade [FILE] [--fix] what to change for this PyWeb version (file and line); --fix applies
|
|
16
23
|
the safe rewrites
|
|
17
24
|
pyweb inspect FILE [--security] where every name runs and why; RPC table; findings
|
|
@@ -66,13 +73,39 @@ pyweb dts FILE.d.ts Python dataclasses from TypeScript declarat
|
|
|
66
73
|
pyweb mcp MCP server over stdio for AI assistants (see AI assistants & MCP)
|
|
67
74
|
pyweb mcp --http [--port 8765] [--token T] the same over Streamable HTTP (127.0.0.1 only without a token)
|
|
68
75
|
pyweb lsp language server over stdio for editors (see below)
|
|
69
|
-
pyweb test [PATH] run pytest
|
|
76
|
+
pyweb test [PATH] run pytest on PATH (a test file, a folder or app.pyweb's folder;
|
|
77
|
+
default: the current folder, where new apps keep test_app.py)
|
|
70
78
|
pyweb fmt [PATH] / pyweb lint [PATH] run ruff format / ruff check (if installed)
|
|
71
79
|
pyweb --version
|
|
72
80
|
```
|
|
73
81
|
|
|
74
82
|
`python -m pyweb.cli ...` is equivalent to `pyweb ...`.
|
|
75
83
|
|
|
84
|
+
## Error messages
|
|
85
|
+
|
|
86
|
+
A compile error names the file and line, shows the line with a caret under
|
|
87
|
+
the problem, suggests the name you probably meant, and links the docs page
|
|
88
|
+
that explains it:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
error: app.pyweb:17: <Greeting> has no prop 'nme'; did you mean 'name'?
|
|
92
|
+
|
|
93
|
+
16 | <h1>{count}</h1>
|
|
94
|
+
> 17 | <Greeting nme="x" />
|
|
95
|
+
| ^
|
|
96
|
+
18 | </main>
|
|
97
|
+
|
|
98
|
+
hint: Use one of the component's parameter names as the attribute.
|
|
99
|
+
docs: https://maanavkrishna.github.io/PyWeb/language.html
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The first line keeps the `file:line: message` shape, so editors and CI
|
|
103
|
+
logs can jump to it. `pyweb check` also warns about kit props and motion
|
|
104
|
+
values that won't work (`<Button variant="outlin">`). The `pyweb dev`
|
|
105
|
+
overlay shows the same, and when page code raises, its error page starts
|
|
106
|
+
with the frames in your own files and their source lines, before the full
|
|
107
|
+
traceback.
|
|
108
|
+
|
|
76
109
|
## Exit codes
|
|
77
110
|
|
|
78
111
|
`check` and `build` exit `1` on compile or security errors and `2` on
|
|
@@ -11,8 +11,9 @@ can decide up front whether they matter for your app.
|
|
|
11
11
|
- **npm packages must ship ES modules.** CommonJS-only packages and
|
|
12
12
|
packages built on Node.js modules can't run in the browser; see
|
|
13
13
|
[npm packages](18-npm-packages.md).
|
|
14
|
-
- **Dev reload
|
|
15
|
-
|
|
14
|
+
- **Dev reload keeps page variables, not component state.** After a save,
|
|
15
|
+
`pyweb dev` reloads the page with its variables, form fields, scroll
|
|
16
|
+
and focus as they were; state inside components starts over.
|
|
16
17
|
- **SQLite is for one machine.** Several processes on one host share it
|
|
17
18
|
fine, but more than one server needs Postgres or MySQL;
|
|
18
19
|
`pyweb check --production` and `pyweb deploy` say so.
|
|
@@ -33,7 +34,9 @@ can decide up front whether they matter for your app.
|
|
|
33
34
|
with Web Push, desktop windows and installers, a UI kit with themes and
|
|
34
35
|
motion, Trusted Types, encrypted fields and a threat model, about 2.5×
|
|
35
36
|
faster serving, Python 3.15 and free-threaded builds, and a much richer
|
|
36
|
-
MCP server.
|
|
37
|
+
MCP server. 0.6.1 added error messages with code frames and
|
|
38
|
+
suggestions, `pyweb doctor`, dev reloads that keep page state, and 4×
|
|
39
|
+
faster reloads and tests.
|
|
37
40
|
- **0.5**: production apps. Models with relations and migrations, forms
|
|
38
41
|
checked in the browser and on the server, an auth kit (passwords,
|
|
39
42
|
passkeys, emailed links, OAuth, two-step sign-in, an admin), row-level
|
|
@@ -64,7 +67,8 @@ Planned, roughly in order. Nothing here is promised for a date.
|
|
|
64
67
|
1. **1.0**: the API, RPC protocol and CLI frozen under semantic versioning,
|
|
65
68
|
after 0.6 has been used in production for a while.
|
|
66
69
|
2. **File uploads from the playground and the admin.**
|
|
67
|
-
3. **
|
|
70
|
+
3. **Hot reload without a page reload** while developing (0.6.1 already
|
|
71
|
+
keeps the page's variables, fields, scroll and focus across reloads).
|
|
68
72
|
|
|
69
73
|
Ideas, bug reports and pull requests are welcome; see
|
|
70
74
|
[CONTRIBUTING.md](https://github.com/MaanavKrishna/PyWeb/blob/main/CONTRIBUTING.md).
|
|
@@ -70,7 +70,7 @@ tool runs. Slow tools (tests, screenshots, profiling) report progress.
|
|
|
70
70
|
| `pyweb_guide` | The rules for writing `.pyweb` apps (or one section): what runs where, state, markup, components, server functions, the browser Python subset, and every common error with its fix. |
|
|
71
71
|
| `pyweb_docs` | Searches the documentation (every page, the guide and the threat model) and returns the matching sections, or one whole page. For any "how do I..." question. |
|
|
72
72
|
| `pyweb_new_app` | Creates an app from a template (`blank`, `counter`, `todo`, `blog`, `saas`, `auth`, `chat`, `ai-chat`, `desktop`) with `AGENTS.md`, `CLAUDE.md`, tests and a stylesheet; apps with a database also get their first migration. `saas` (teams, accounts, tenant Models, forms, live pages, jobs, webhooks, admin) is the best start for a product; `desktop` is a notes app for `pyweb desktop`. |
|
|
73
|
-
| `pyweb_check` | Compiles the app and runs the security and accessibility checks. Returns `ok`, errors with `line`, `message
|
|
73
|
+
| `pyweb_check` | Compiles the app and runs the security and accessibility checks. Returns `ok`, errors with `line`, `message`, a fix `hint`, a code `frame` and a `docs` link, plus pages (with their layouts, live-data variables and npm packages), signals, what's sent to the browser, JS size and the server functions. |
|
|
74
74
|
| `pyweb_explain` | What your editor shows for a line: for each name, where it runs and why (or its signature or npm type), the problems on that line and, with `complete`, the completions at a column. |
|
|
75
75
|
| `pyweb_inspect` | For every name: does it run in the browser or on the server, and why. |
|
|
76
76
|
| `pyweb_compiled` | The JavaScript generated for a page, and its server-rendered HTML. |
|
|
@@ -106,6 +106,7 @@ tool runs. Slow tools (tests, screenshots, profiling) report progress.
|
|
|
106
106
|
| Tool | What it does |
|
|
107
107
|
|---|---|
|
|
108
108
|
| `pyweb_dev` | Whether `pyweb dev` is running for this app: its URL, whether the app compiles right now (with the error and traceback), its pages and recent 500 errors. |
|
|
109
|
+
| `pyweb_doctor` | Project health, as `pyweb doctor`: each check with `status` (ok, warn, fail), `detail` and a `fix`; `production=true` adds the production settings. |
|
|
109
110
|
| `pyweb_logs` | The dev server's log lines (warnings, errors with tracebacks, slow queries, job failures); `since` returns only newer ones. |
|
|
110
111
|
|
|
111
112
|
When `pyweb dev` starts, it writes `.pyweb/dev.json` next to the app: its
|
|
@@ -107,12 +107,22 @@ you use it in a browser.
|
|
|
107
107
|
Profile against a copy of your real data. A page that's fast with ten
|
|
108
108
|
rows can be slow with ten thousand.
|
|
109
109
|
|
|
110
|
+
## Fast reloads and tests
|
|
111
|
+
|
|
112
|
+
The UI kit is compiled once per process, and each `.pyweb` file's Python
|
|
113
|
+
code is reused until the file changes. On the saas example that took a
|
|
114
|
+
reload in `pyweb dev` from about 255 to 60 ms and creating a
|
|
115
|
+
`TestClient` from about 255 to 55 ms; the dev server also notices a save
|
|
116
|
+
within a tenth of a second. Compiled output is byte-for-byte what a fresh
|
|
117
|
+
compile gives, and a test checks that.
|
|
118
|
+
|
|
110
119
|
## Benchmarks in CI
|
|
111
120
|
|
|
112
121
|
`python -m pyweb.bench` reports compile time, shipped JavaScript and
|
|
113
122
|
render time per page. `--system` measures the whole stack: requests per
|
|
114
123
|
second over real sockets, a signed-in page with a database, start-up
|
|
115
|
-
time,
|
|
124
|
+
time, live query hashing, and how long loading an app again takes (what
|
|
125
|
+
`pyweb dev` does after a save, and what each `TestClient` does).
|
|
116
126
|
|
|
117
127
|
PyWeb's CI runs `--compare` on every push and pull request. It checks out
|
|
118
128
|
the commit before the change next to the new one and runs both five
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyweb-stack"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.1"
|
|
8
8
|
description = "Full-stack web apps in one Python file: server-rendered pages, reactive browser UI, typed RPC."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -66,7 +66,7 @@ pyweb = "pyweb.cli:main"
|
|
|
66
66
|
include = ["pyweb*"]
|
|
67
67
|
|
|
68
68
|
[tool.setuptools.package-data]
|
|
69
|
-
pyweb = ["runtime/browser/*.js", "runtime/browser/ui/*.js", "ui/kit/*.pyweb", "ui/static/*", "py.typed", "ai/*.md", "templates/*.pyweb", "templates/*.css", "templates/*/*.pyweb", "templates/*/*.py", "templates/*/*.md"]
|
|
69
|
+
pyweb = ["runtime/browser/*.js", "runtime/browser/ui/*.js", "runtime/browser/dev/*.js", "ui/kit/*.pyweb", "ui/static/*", "py.typed", "ai/*.md", "templates/*.pyweb", "templates/*.css", "templates/*/*.pyweb", "templates/*/*.py", "templates/*/*.md"]
|
|
70
70
|
|
|
71
71
|
[tool.pytest.ini_options]
|
|
72
72
|
testpaths = ["tests"]
|
|
@@ -30,10 +30,10 @@ def _state_function(fn):
|
|
|
30
30
|
Markup placeholders are dropped; an explicit ``return`` (e.g.
|
|
31
31
|
``return redirect("/login")``) still returns early.
|
|
32
32
|
"""
|
|
33
|
-
new = copy.
|
|
33
|
+
new = copy.copy(fn) # only the kept statements need copies, not the markup
|
|
34
34
|
new.name = STATE_PREFIX + fn.name
|
|
35
35
|
new.decorator_list = []
|
|
36
|
-
body = [s for s in
|
|
36
|
+
body = [copy.deepcopy(s) for s in fn.body if not is_ui_stmt(s)]
|
|
37
37
|
ret = ast.Return(value=ast.Call(func=ast.Name(id="locals", ctx=ast.Load()), args=[], keywords=[]))
|
|
38
38
|
body.append(ret)
|
|
39
39
|
new.body = body
|
|
@@ -41,6 +41,28 @@ def _state_function(fn):
|
|
|
41
41
|
return new
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
_CODE: dict = {} # (file, source hash, page/component names) -> code object
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _module_code(source, filename, infos):
|
|
48
|
+
"""The Python code for a ``.pyweb`` module: its Python part plus a state function per page and
|
|
49
|
+
component. Cached by source, so reloading an app (or loading it again in tests) skips re-parsing
|
|
50
|
+
the files that didn't change, the UI kit's above all."""
|
|
51
|
+
import hashlib
|
|
52
|
+
key = (filename, hashlib.sha256(source.encode()).hexdigest(), tuple(getattr(i, "name", "") for i in infos))
|
|
53
|
+
code = _CODE.get(key)
|
|
54
|
+
if code is None:
|
|
55
|
+
py_source, _ui = P.split_sources(source)
|
|
56
|
+
tree = ast.parse(py_source, filename=filename)
|
|
57
|
+
for info in infos:
|
|
58
|
+
tree.body.append(_state_function(info.node))
|
|
59
|
+
code = compile(tree, filename, "exec")
|
|
60
|
+
if len(_CODE) > 256:
|
|
61
|
+
_CODE.clear()
|
|
62
|
+
_CODE[key] = code
|
|
63
|
+
return code
|
|
64
|
+
|
|
65
|
+
|
|
44
66
|
def _kit_markup(path):
|
|
45
67
|
"""Whether a component file is kit markup (a component copied with ``pyweb ui add``)."""
|
|
46
68
|
try:
|
|
@@ -169,10 +191,7 @@ class LoadedApp:
|
|
|
169
191
|
|
|
170
192
|
# ----------------------------------------------------------- module
|
|
171
193
|
def _exec(self, source, filename, infos, module_name=None):
|
|
172
|
-
|
|
173
|
-
tree = ast.parse(py_source, filename=filename)
|
|
174
|
-
for info in infos:
|
|
175
|
-
tree.body.append(_state_function(info.node))
|
|
194
|
+
code = _module_code(source, filename, infos)
|
|
176
195
|
stem = os.path.splitext(os.path.basename(filename))[0] or "app"
|
|
177
196
|
mod = types.ModuleType(module_name or f"pyweb_app_{stem}")
|
|
178
197
|
mod.__file__ = os.path.abspath(filename) if self.path else filename
|
|
@@ -191,7 +210,7 @@ class LoadedApp:
|
|
|
191
210
|
if app_dir and app_dir not in sys.path:
|
|
192
211
|
sys.path.insert(0, app_dir)
|
|
193
212
|
sys.modules[mod.__name__] = mod
|
|
194
|
-
exec(
|
|
213
|
+
exec(code, mod.__dict__) # noqa: S102 - the app itself
|
|
195
214
|
return mod
|
|
196
215
|
|
|
197
216
|
# -------------------------------------------------------- rendering
|
|
@@ -112,6 +112,8 @@ SYSTEM = {
|
|
|
112
112
|
"import_ms": (False, 5.0),
|
|
113
113
|
"cli_version_ms": (False, 8.0),
|
|
114
114
|
"live_10k_ms": (False, 2.0),
|
|
115
|
+
"reload_ms": (False, 15.0),
|
|
116
|
+
"test_client_ms": (False, 15.0),
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
|
|
@@ -225,6 +227,38 @@ def startup_ms(rounds=7):
|
|
|
225
227
|
round(max(0.0, wall(["-m", "pyweb.cli", "--version"]) - floor), 1))
|
|
226
228
|
|
|
227
229
|
|
|
230
|
+
def reload_ms(rounds=5):
|
|
231
|
+
"""Loading the saas app again in a warm process (what ``pyweb dev`` does after a save) and creating a
|
|
232
|
+
``TestClient`` for it, in milliseconds."""
|
|
233
|
+
import os
|
|
234
|
+
import shutil
|
|
235
|
+
import tempfile
|
|
236
|
+
from pyweb.hosting import Site
|
|
237
|
+
from pyweb.testing import TestClient
|
|
238
|
+
old = os.getcwd()
|
|
239
|
+
fixed = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "bench", "apps", "saas")
|
|
240
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
241
|
+
try:
|
|
242
|
+
if os.path.isdir(fixed):
|
|
243
|
+
shutil.copytree(fixed, os.path.join(tmp, "team"))
|
|
244
|
+
else:
|
|
245
|
+
from pyweb.mcp import scaffold
|
|
246
|
+
scaffold(os.path.join(tmp, "team"), template="saas")
|
|
247
|
+
os.chdir(os.path.join(tmp, "team"))
|
|
248
|
+
Site("app.pyweb") # the first load fills the caches
|
|
249
|
+
reloads, clients = [], []
|
|
250
|
+
for _ in range(rounds):
|
|
251
|
+
t0 = time.perf_counter()
|
|
252
|
+
Site("app.pyweb")
|
|
253
|
+
reloads.append((time.perf_counter() - t0) * 1000)
|
|
254
|
+
t0 = time.perf_counter()
|
|
255
|
+
TestClient("app.pyweb")
|
|
256
|
+
clients.append((time.perf_counter() - t0) * 1000)
|
|
257
|
+
return round(_median(reloads), 1), round(_median(clients), 1)
|
|
258
|
+
finally:
|
|
259
|
+
os.chdir(old)
|
|
260
|
+
|
|
261
|
+
|
|
228
262
|
def live_10k_ms():
|
|
229
263
|
"""Version hash plus row diff for a 10,000-row live query, in milliseconds."""
|
|
230
264
|
from pyweb.livedata import _version, diff
|
|
@@ -244,8 +278,9 @@ def system():
|
|
|
244
278
|
import os
|
|
245
279
|
os.environ.setdefault("PYWEB_WORKER", "0")
|
|
246
280
|
imp, cli = startup_ms()
|
|
281
|
+
reload, client = reload_ms()
|
|
247
282
|
return {"network_rps": network_rps(), "saas_page_us": saas_page_us(), "import_ms": imp,
|
|
248
|
-
"cli_version_ms": cli, "live_10k_ms": live_10k_ms()}
|
|
283
|
+
"cli_version_ms": cli, "live_10k_ms": live_10k_ms(), "reload_ms": reload, "test_client_ms": client}
|
|
249
284
|
|
|
250
285
|
|
|
251
286
|
def regressions(base, head, *, tolerance=0.15):
|
|
@@ -14,6 +14,17 @@ def _load(path):
|
|
|
14
14
|
return fh.read()
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def _pretty_error(exc, path=None):
|
|
18
|
+
"""A compile error as terminal text: where, the code frame, a hint and a docs link."""
|
|
19
|
+
from pyweb.compiler.errors import pretty
|
|
20
|
+
path = getattr(exc, "filename", None) or path
|
|
21
|
+
try:
|
|
22
|
+
source = _load(path) if path else None
|
|
23
|
+
except OSError:
|
|
24
|
+
source = None
|
|
25
|
+
return pretty(exc, source, path)
|
|
26
|
+
|
|
27
|
+
|
|
17
28
|
def cmd_inspect(args):
|
|
18
29
|
from pyweb.compiler import compile_source
|
|
19
30
|
src = _load(args.file)
|
|
@@ -41,6 +52,12 @@ def cmd_inspect(args):
|
|
|
41
52
|
print(f"{finding['kind']} {args.file}:{finding['line']}: {finding['message']}")
|
|
42
53
|
|
|
43
54
|
|
|
55
|
+
def cmd_doctor(args):
|
|
56
|
+
"""Is this project and this machine ready? One line per check, with a fix for each problem."""
|
|
57
|
+
from pyweb import doctor
|
|
58
|
+
raise SystemExit(doctor.main(args.file, production=args.production, as_json=args.json, port=args.port))
|
|
59
|
+
|
|
60
|
+
|
|
44
61
|
def cmd_check(args):
|
|
45
62
|
from pyweb.compiler import compile_source
|
|
46
63
|
from pyweb.security import check_source
|
|
@@ -48,17 +65,22 @@ def cmd_check(args):
|
|
|
48
65
|
src = _load(args.file)
|
|
49
66
|
findings = check_source(src, args.file)
|
|
50
67
|
findings += scan_app(args.file)
|
|
51
|
-
from pyweb import a11y
|
|
52
|
-
findings += a11y.check(src, args.file)
|
|
68
|
+
from pyweb import a11y, lsp_ui
|
|
69
|
+
findings += a11y.check(src, args.file) + lsp_ui.check(src, args.file)
|
|
53
70
|
try:
|
|
54
71
|
out = compile_source(src, filename=args.file)
|
|
55
72
|
n_sig = sum(len(p["signals"]) for p in out["pages"].values())
|
|
56
73
|
print(f"ok: {len(out['pages'])} page(s), {n_sig} signal(s), {len(out['rpc'])} rpc(s)")
|
|
57
74
|
except Exception as exc: # noqa: BLE001
|
|
58
|
-
|
|
59
|
-
|
|
75
|
+
from pyweb.compiler.errors import describe
|
|
76
|
+
rec = describe(exc, src, args.file)
|
|
77
|
+
findings.append({"kind": "compile-error", "line": rec.get("line") or 0, "message": rec["message"],
|
|
78
|
+
"detail": "\n".join(x for x in (rec.get("frame"), rec.get("hint") and "hint: " + rec["hint"],
|
|
79
|
+
rec.get("docs") and "docs: " + rec["docs"]) if x)})
|
|
60
80
|
for f in findings:
|
|
61
81
|
print(f"{f['kind']} {f.get('file') or args.file}:{f['line']}: {f['message']}")
|
|
82
|
+
if f.get("detail"):
|
|
83
|
+
print(" " + f["detail"].replace("\n", "\n "))
|
|
62
84
|
problems = production_problems(src, os.path.dirname(os.path.abspath(args.file))) \
|
|
63
85
|
if getattr(args, "production", False) else []
|
|
64
86
|
for p in problems:
|
|
@@ -230,32 +252,44 @@ def cmd_build(args):
|
|
|
230
252
|
raise SystemExit(2)
|
|
231
253
|
|
|
232
254
|
|
|
233
|
-
|
|
255
|
+
def _dev_reload_js():
|
|
256
|
+
path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "runtime", "browser", "dev",
|
|
257
|
+
"reload.js")
|
|
258
|
+
with open(path, encoding="utf-8") as fh:
|
|
259
|
+
return "<script>" + fh.read().replace("</", "<\\/") + "</script>"
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
DEV_RELOAD_JS = _dev_reload_js()
|
|
234
263
|
|
|
235
264
|
|
|
236
265
|
def _error_overlay(exc, path):
|
|
237
266
|
"""HTML page shown in the browser while the app has a compile error."""
|
|
238
267
|
import html as _h
|
|
239
|
-
|
|
240
|
-
snippet = ""
|
|
268
|
+
from pyweb.compiler.errors import describe
|
|
241
269
|
try:
|
|
242
|
-
|
|
243
|
-
if lineno:
|
|
244
|
-
lo, hi = max(0, lineno - 4), min(len(lines), lineno + 3)
|
|
245
|
-
rows = []
|
|
246
|
-
for i in range(lo, hi):
|
|
247
|
-
mark = ">" if i + 1 == lineno else " "
|
|
248
|
-
rows.append(f"{mark} {i + 1:4d} | {_h.escape(lines[i])}")
|
|
249
|
-
snippet = "<pre class=code>" + "\n".join(rows) + "</pre>"
|
|
270
|
+
source = _load(getattr(exc, "filename", None) or path)
|
|
250
271
|
except OSError:
|
|
251
|
-
|
|
272
|
+
source = None
|
|
273
|
+
rec = describe(exc, source, path)
|
|
274
|
+
where = rec.get("file") or path
|
|
275
|
+
if rec.get("line"):
|
|
276
|
+
where += f":{rec['line']}"
|
|
277
|
+
parts = [f"<h1>{_h.escape(type(exc).__name__)}</h1>",
|
|
278
|
+
f"<p class=where>{_h.escape(where)}</p><pre>{_h.escape(rec['message'])}</pre>"]
|
|
279
|
+
if rec.get("frame"):
|
|
280
|
+
parts.append(f"<pre class=code>{_h.escape(rec['frame'])}</pre>")
|
|
281
|
+
if rec.get("hint"):
|
|
282
|
+
parts.append(f"<p class=hint><b>Hint:</b> {_h.escape(rec['hint'])}</p>")
|
|
283
|
+
if rec.get("docs"):
|
|
284
|
+
parts.append(f'<p><a href="{_h.escape(rec["docs"])}" target=_blank rel=noopener>Read the docs</a></p>')
|
|
252
285
|
return ("<!doctype html><meta charset=utf-8><title>PyWeb error</title>"
|
|
253
286
|
"<style>body{font:15px/1.5 system-ui;margin:0;background:#1b1b1f;color:#eee}"
|
|
254
287
|
"main{max-width:880px;margin:8vh auto;padding:0 24px}h1{color:#ff6b6b;font-size:20px}"
|
|
255
288
|
"pre{background:#0f0f12;padding:16px;border-radius:8px;overflow:auto;font-size:13px}"
|
|
256
|
-
".code{border-left:3px solid #ff6b6b}
|
|
257
|
-
|
|
258
|
-
|
|
289
|
+
".code{border-left:3px solid #ff6b6b}.where{color:#aaa;margin:0}a{color:#8ab4ff}"
|
|
290
|
+
".hint{background:#26262c;padding:10px 14px;border-radius:8px}</style><main>"
|
|
291
|
+
+ "".join(parts)
|
|
292
|
+
+ "<p>Fix the file and save: this page reloads automatically.</p></main>" + DEV_RELOAD_JS)
|
|
259
293
|
|
|
260
294
|
|
|
261
295
|
class _DevState:
|
|
@@ -346,7 +380,7 @@ def cmd_dev(args):
|
|
|
346
380
|
link = Link(state)
|
|
347
381
|
_dev_load(state, Site)
|
|
348
382
|
if state.error is not None:
|
|
349
|
-
print(
|
|
383
|
+
print("error: " + _pretty_error(state.error, state.path), file=sys.stderr)
|
|
350
384
|
host = getattr(args, "host", "127.0.0.1") or "127.0.0.1"
|
|
351
385
|
|
|
352
386
|
def ready(port):
|
|
@@ -395,14 +429,14 @@ def _watch_and_reload(state, Site):
|
|
|
395
429
|
def poll():
|
|
396
430
|
last = snapshot()
|
|
397
431
|
while True:
|
|
398
|
-
time.sleep(0.
|
|
432
|
+
time.sleep(0.1)
|
|
399
433
|
now = snapshot()
|
|
400
434
|
if now != last:
|
|
401
435
|
last = now
|
|
402
436
|
purge_local_modules()
|
|
403
437
|
_dev_load(state, Site)
|
|
404
438
|
if state.error is not None:
|
|
405
|
-
print(
|
|
439
|
+
print("error: " + _pretty_error(state.error, state.path), flush=True)
|
|
406
440
|
else:
|
|
407
441
|
print(f"reloaded {state.path}", flush=True)
|
|
408
442
|
|
|
@@ -495,13 +529,25 @@ def cmd_serve(args):
|
|
|
495
529
|
_net.run(make_site, host=args.host, port=args.port, workers=workers, logger=logger, ready=ready)
|
|
496
530
|
|
|
497
531
|
|
|
532
|
+
def tests_path(path=None):
|
|
533
|
+
"""Where ``pyweb test`` runs pytest: the given file or folder, the folder of a ``.pyweb`` file,
|
|
534
|
+
or the current folder (templates keep ``test_app.py`` next to ``app.pyweb``)."""
|
|
535
|
+
target = os.path.abspath(path or ".")
|
|
536
|
+
if target.endswith(".pyweb"):
|
|
537
|
+
target = os.path.dirname(target)
|
|
538
|
+
return target
|
|
539
|
+
|
|
540
|
+
|
|
498
541
|
def cmd_test(args):
|
|
542
|
+
import importlib.util
|
|
499
543
|
import subprocess
|
|
500
544
|
import sys as _sys
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
545
|
+
if importlib.util.find_spec("pytest") is None:
|
|
546
|
+
raise SystemExit("pyweb test runs pytest, which isn't installed: pip install pytest")
|
|
547
|
+
target = tests_path(getattr(args, "path", None))
|
|
548
|
+
if not os.path.exists(target):
|
|
549
|
+
raise SystemExit(f"pyweb test: no such file or folder: {args.path}")
|
|
550
|
+
raise SystemExit(subprocess.call([_sys.executable, "-m", "pytest", target, "-q"]))
|
|
505
551
|
|
|
506
552
|
|
|
507
553
|
def cmd_fmt(args):
|
|
@@ -983,6 +1029,12 @@ def main(argv=None):
|
|
|
983
1029
|
p.add_argument("--token", default=None, help="--http: require Authorization: Bearer TOKEN (or PYWEB_MCP_TOKEN)")
|
|
984
1030
|
p.set_defaults(fn=cmd_mcp)
|
|
985
1031
|
p = sub.add_parser("lsp", help="run the language server (stdio) for editors"); p.set_defaults(fn=cmd_lsp)
|
|
1032
|
+
p = sub.add_parser("doctor", help="check the project and the machine, with a fix for each problem")
|
|
1033
|
+
p.add_argument("file", nargs="?", default="app.pyweb")
|
|
1034
|
+
p.add_argument("--production", action="store_true", help="also check the environment for production")
|
|
1035
|
+
p.add_argument("--json", action="store_true", help="print the checks as JSON")
|
|
1036
|
+
p.add_argument("--port", type=int, default=8000, help="the port pyweb dev will use (default 8000)")
|
|
1037
|
+
p.set_defaults(fn=cmd_doctor)
|
|
986
1038
|
p = sub.add_parser("check"); p.add_argument("file"); p.add_argument("--strict", action="store_true", help="exit 1 on any finding (security warnings too)")
|
|
987
1039
|
p.add_argument("--production", action="store_true", help="also check the environment is ready for production")
|
|
988
1040
|
p.set_defaults(fn=cmd_check)
|
|
@@ -1036,12 +1088,7 @@ def main(argv=None):
|
|
|
1036
1088
|
print(f"error: {exc}", file=sys.stderr)
|
|
1037
1089
|
raise SystemExit(1)
|
|
1038
1090
|
except (CompileError, SyntaxError) as exc:
|
|
1039
|
-
|
|
1040
|
-
where = f"{exc.filename or getattr(args, 'file', '')}:{exc.lineno}" if exc.lineno else ""
|
|
1041
|
-
msg = getattr(exc, "pyweb_msg", None) or exc.msg
|
|
1042
|
-
print(f"error: {where}: {msg}" if where else f"error: {msg}", file=sys.stderr)
|
|
1043
|
-
else:
|
|
1044
|
-
print(f"error: {exc}", file=sys.stderr)
|
|
1091
|
+
print("error: " + _pretty_error(exc, getattr(args, "file", None)), file=sys.stderr)
|
|
1045
1092
|
raise SystemExit(1)
|
|
1046
1093
|
|
|
1047
1094
|
|