pyweb-stack 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. pyweb_stack-0.1.0/LICENSE +21 -0
  2. pyweb_stack-0.1.0/PKG-INFO +198 -0
  3. pyweb_stack-0.1.0/README.md +145 -0
  4. pyweb_stack-0.1.0/pyproject.toml +72 -0
  5. pyweb_stack-0.1.0/pyweb/__init__.py +71 -0
  6. pyweb_stack-0.1.0/pyweb/app.py +37 -0
  7. pyweb_stack-0.1.0/pyweb/app_loader.py +139 -0
  8. pyweb_stack-0.1.0/pyweb/asgi.py +71 -0
  9. pyweb_stack-0.1.0/pyweb/auth.py +701 -0
  10. pyweb_stack-0.1.0/pyweb/bench.py +101 -0
  11. pyweb_stack-0.1.0/pyweb/browser.py +232 -0
  12. pyweb_stack-0.1.0/pyweb/build.py +295 -0
  13. pyweb_stack-0.1.0/pyweb/cache.py +201 -0
  14. pyweb_stack-0.1.0/pyweb/cli/__init__.py +465 -0
  15. pyweb_stack-0.1.0/pyweb/cli/__main__.py +3 -0
  16. pyweb_stack-0.1.0/pyweb/compiler/__init__.py +5 -0
  17. pyweb_stack-0.1.0/pyweb/compiler/ast.py +45 -0
  18. pyweb_stack-0.1.0/pyweb/compiler/codegen/__init__.py +9 -0
  19. pyweb_stack-0.1.0/pyweb/compiler/codegen/ir.py +50 -0
  20. pyweb_stack-0.1.0/pyweb/compiler/errors.py +29 -0
  21. pyweb_stack-0.1.0/pyweb/compiler/lower.py +733 -0
  22. pyweb_stack-0.1.0/pyweb/compiler/parser.py +532 -0
  23. pyweb_stack-0.1.0/pyweb/compiler/pipeline.py +158 -0
  24. pyweb_stack-0.1.0/pyweb/compiler/pyjs.py +1087 -0
  25. pyweb_stack-0.1.0/pyweb/compiler/rpc.py +35 -0
  26. pyweb_stack-0.1.0/pyweb/context.py +193 -0
  27. pyweb_stack-0.1.0/pyweb/css.py +115 -0
  28. pyweb_stack-0.1.0/pyweb/db/__init__.py +652 -0
  29. pyweb_stack-0.1.0/pyweb/db/migrate.py +143 -0
  30. pyweb_stack-0.1.0/pyweb/decorators.py +49 -0
  31. pyweb_stack-0.1.0/pyweb/deploy.py +102 -0
  32. pyweb_stack-0.1.0/pyweb/forms.py +108 -0
  33. pyweb_stack-0.1.0/pyweb/hosting.py +139 -0
  34. pyweb_stack-0.1.0/pyweb/jobs.py +248 -0
  35. pyweb_stack-0.1.0/pyweb/live.py +88 -0
  36. pyweb_stack-0.1.0/pyweb/lsp.py +214 -0
  37. pyweb_stack-0.1.0/pyweb/models.py +269 -0
  38. pyweb_stack-0.1.0/pyweb/npm.py +270 -0
  39. pyweb_stack-0.1.0/pyweb/observability.py +313 -0
  40. pyweb_stack-0.1.0/pyweb/platform.py +69 -0
  41. pyweb_stack-0.1.0/pyweb/plugins.py +112 -0
  42. pyweb_stack-0.1.0/pyweb/py.typed +0 -0
  43. pyweb_stack-0.1.0/pyweb/realtime.py +189 -0
  44. pyweb_stack-0.1.0/pyweb/rpc.py +137 -0
  45. pyweb_stack-0.1.0/pyweb/runtime/browser/runtime.js +1151 -0
  46. pyweb_stack-0.1.0/pyweb/runtime/server/__init__.py +445 -0
  47. pyweb_stack-0.1.0/pyweb/security.py +162 -0
  48. pyweb_stack-0.1.0/pyweb/serve.py +335 -0
  49. pyweb_stack-0.1.0/pyweb/ssr.py +260 -0
  50. pyweb_stack-0.1.0/pyweb/sync.py +118 -0
  51. pyweb_stack-0.1.0/pyweb/testing.py +154 -0
  52. pyweb_stack-0.1.0/pyweb/uploads.py +94 -0
  53. pyweb_stack-0.1.0/pyweb_stack.egg-info/PKG-INFO +198 -0
  54. pyweb_stack-0.1.0/pyweb_stack.egg-info/SOURCES.txt +94 -0
  55. pyweb_stack-0.1.0/pyweb_stack.egg-info/dependency_links.txt +1 -0
  56. pyweb_stack-0.1.0/pyweb_stack.egg-info/entry_points.txt +2 -0
  57. pyweb_stack-0.1.0/pyweb_stack.egg-info/requires.txt +26 -0
  58. pyweb_stack-0.1.0/pyweb_stack.egg-info/top_level.txt +1 -0
  59. pyweb_stack-0.1.0/setup.cfg +4 -0
  60. pyweb_stack-0.1.0/tests/test_auth.py +59 -0
  61. pyweb_stack-0.1.0/tests/test_auth_contract.py +84 -0
  62. pyweb_stack-0.1.0/tests/test_auth_security.py +203 -0
  63. pyweb_stack-0.1.0/tests/test_auth_v1.py +163 -0
  64. pyweb_stack-0.1.0/tests/test_backplane.py +141 -0
  65. pyweb_stack-0.1.0/tests/test_browser_build.py +147 -0
  66. pyweb_stack-0.1.0/tests/test_budget.py +52 -0
  67. pyweb_stack-0.1.0/tests/test_cli_deploy.py +43 -0
  68. pyweb_stack-0.1.0/tests/test_codegen.py +77 -0
  69. pyweb_stack-0.1.0/tests/test_compiler.py +108 -0
  70. pyweb_stack-0.1.0/tests/test_db_production.py +173 -0
  71. pyweb_stack-0.1.0/tests/test_deploy_upload_render.py +69 -0
  72. pyweb_stack-0.1.0/tests/test_docs.py +30 -0
  73. pyweb_stack-0.1.0/tests/test_dts.py +76 -0
  74. pyweb_stack-0.1.0/tests/test_e2e.py +222 -0
  75. pyweb_stack-0.1.0/tests/test_examples.py +32 -0
  76. pyweb_stack-0.1.0/tests/test_forms_security_obs.py +95 -0
  77. pyweb_stack-0.1.0/tests/test_jobs_cache_realtime.py +151 -0
  78. pyweb_stack-0.1.0/tests/test_lsp.py +81 -0
  79. pyweb_stack-0.1.0/tests/test_lsp_state.py +76 -0
  80. pyweb_stack-0.1.0/tests/test_models_db.py +84 -0
  81. pyweb_stack-0.1.0/tests/test_observability.py +89 -0
  82. pyweb_stack-0.1.0/tests/test_parser.py +71 -0
  83. pyweb_stack-0.1.0/tests/test_parser_v1.py +139 -0
  84. pyweb_stack-0.1.0/tests/test_plugins_platform_bench.py +78 -0
  85. pyweb_stack-0.1.0/tests/test_production_gaps.py +538 -0
  86. pyweb_stack-0.1.0/tests/test_pyjs_semantics.py +108 -0
  87. pyweb_stack-0.1.0/tests/test_reactivity.py +45 -0
  88. pyweb_stack-0.1.0/tests/test_realtime_transport.py +77 -0
  89. pyweb_stack-0.1.0/tests/test_reference_apps.py +111 -0
  90. pyweb_stack-0.1.0/tests/test_rpc_placement.py +83 -0
  91. pyweb_stack-0.1.0/tests/test_rpc_production.py +134 -0
  92. pyweb_stack-0.1.0/tests/test_runtime_signals.py +113 -0
  93. pyweb_stack-0.1.0/tests/test_server_db.py +173 -0
  94. pyweb_stack-0.1.0/tests/test_static_hashing.py +70 -0
  95. pyweb_stack-0.1.0/tests/test_sync_live.py +113 -0
  96. pyweb_stack-0.1.0/tests/test_website.py +74 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MaanavKrishna and Claude
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyweb-stack
3
+ Version: 0.1.0
4
+ Summary: Full-stack web apps in one Python file: server-rendered pages, reactive browser UI, typed RPC.
5
+ Author-email: MaanavKrishna <67054795+MaanavKrishna@users.noreply.github.com>, Claude <noreply@anthropic.com>
6
+ Maintainer-email: MaanavKrishna <67054795+MaanavKrishna@users.noreply.github.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://maanavkrishna.github.io/PyWeb/
9
+ Project-URL: Documentation, https://maanavkrishna.github.io/PyWeb/guide.html
10
+ Project-URL: Source, https://github.com/MaanavKrishna/PyWeb
11
+ Project-URL: Changelog, https://github.com/MaanavKrishna/PyWeb/blob/main/CHANGELOG.md
12
+ Project-URL: Issues, https://github.com/MaanavKrishna/PyWeb/issues
13
+ Keywords: web,framework,full-stack,reactive,rpc,ssr,compiler
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Web Environment
16
+ Classifier: Framework :: AsyncIO
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: JavaScript
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
28
+ Classifier: Topic :: Software Development :: Compilers
29
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
30
+ Requires-Python: >=3.10
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Provides-Extra: postgres
34
+ Requires-Dist: psycopg[binary]>=3.1; extra == "postgres"
35
+ Provides-Extra: mysql
36
+ Requires-Dist: PyMySQL>=1.1; extra == "mysql"
37
+ Provides-Extra: redis
38
+ Requires-Dist: redis>=5; extra == "redis"
39
+ Provides-Extra: crypto
40
+ Requires-Dist: cryptography>=42; extra == "crypto"
41
+ Provides-Extra: asgi
42
+ Requires-Dist: uvicorn>=0.30; extra == "asgi"
43
+ Provides-Extra: all
44
+ Requires-Dist: psycopg[binary]>=3.1; extra == "all"
45
+ Requires-Dist: PyMySQL>=1.1; extra == "all"
46
+ Requires-Dist: redis>=5; extra == "all"
47
+ Requires-Dist: cryptography>=42; extra == "all"
48
+ Requires-Dist: uvicorn>=0.30; extra == "all"
49
+ Provides-Extra: test
50
+ Requires-Dist: pytest>=8; extra == "test"
51
+ Requires-Dist: playwright>=1.45; extra == "test"
52
+ Dynamic: license-file
53
+
54
+ # PyWeb
55
+
56
+ **Full-stack web apps in one Python file.** Server-rendered pages,
57
+ reactive browser UI compiled from Python, and typed calls to server
58
+ functions, without a JavaScript toolchain.
59
+
60
+ [![CI](https://github.com/MaanavKrishna/PyWeb/actions/workflows/ci.yml/badge.svg)](https://github.com/MaanavKrishna/PyWeb/actions/workflows/ci.yml)
61
+ [![Docs](https://img.shields.io/badge/docs-maanavkrishna.github.io%2FPyWeb-0d9488)](https://maanavkrishna.github.io/PyWeb/)
62
+ ![Python](https://img.shields.io/badge/python-3.10%E2%80%933.13-3776ab)
63
+ ![PyPI](https://img.shields.io/pypi/v/pyweb-stack)
64
+ ![License](https://img.shields.io/badge/license-MIT-lightgrey)
65
+
66
+ ```pyweb
67
+ from pyweb import App, server
68
+
69
+ app = App(title="Guestbook")
70
+ ENTRIES = []
71
+
72
+
73
+ @server
74
+ def sign(name: str) -> list:
75
+ ENTRIES.append(name.strip() or "anonymous")
76
+ return ENTRIES
77
+
78
+
79
+ @app.page("/")
80
+ def Home():
81
+ entries = list(ENTRIES) # computed on the server per request
82
+ name = "" # bound to the input: browser state
83
+
84
+ def submit(): # compiled to JavaScript
85
+ entries = sign(name) # typed RPC to the server
86
+ name = ""
87
+
88
+ <main>
89
+ <h1>Guestbook ({len(entries)})</h1>
90
+ <form onsubmit={submit}>
91
+ <input bind={name} placeholder="Your name" />
92
+ <button>Sign</button>
93
+ </form>
94
+ <ul>
95
+ for entry in entries:
96
+ <li>{entry}</li>
97
+ </ul>
98
+ </main>
99
+ ```
100
+
101
+ ```bash
102
+ pip install pyweb-stack # imported as `pyweb`
103
+ pyweb dev app.pyweb # http://localhost:8000
104
+ ```
105
+
106
+ ## Why PyWeb
107
+
108
+ - **Interactions run in the browser.** Handlers and expressions are
109
+ compiled to small JavaScript modules; the server is only contacted when
110
+ your code calls a `@server` function. No WebSocket per user, no
111
+ multi-megabyte Python runtime in the browser.
112
+ - **Every page is server-rendered.** Complete HTML on first paint, real
113
+ data from your database, good for SEO and slow devices.
114
+ - **You never write an API layer.** `@server` functions get endpoints,
115
+ argument validation, typed errors and generated browser calls.
116
+ - **Plain variables are state.** The compiler sees which variables your
117
+ handlers change and makes exactly those reactive; updates touch only
118
+ the DOM nodes that read them.
119
+ - **Boundaries are checked.** Database handles, imports and secrets can't
120
+ leak into browser code: it's a compile error with a line number.
121
+ `pyweb inspect` explains where every name runs and why.
122
+ - **Stateless servers.** Signed-cookie sessions and plain HTTP RPC scale
123
+ horizontally behind any load balancer. Deploy with `pyweb serve`,
124
+ uvicorn/gunicorn (ASGI) or the generated Dockerfile.
125
+
126
+ A typical interactive page ships under 1 KB of page code plus a ~10 KB
127
+ (gzip) runtime that's cached across pages. Pages without interactivity
128
+ ship no JavaScript.
129
+
130
+ ## Is it for you?
131
+
132
+ **Good fit:** internal tools, admin panels, dashboards, CRUD apps,
133
+ small SaaS products and content sites with interactive parts, built by
134
+ people who'd rather stay in Python.
135
+
136
+ **Not a fit:** large client-heavy single-page apps that need the npm
137
+ ecosystem (use React/Svelte/Vue), or running scientific Python in the
138
+ browser (use Pyodide/PyScript). See the
139
+ [comparison](https://maanavkrishna.github.io/PyWeb/introduction.html)
140
+ and [current limitations](docs/16-limitations-roadmap.md).
141
+
142
+ ## Documentation
143
+
144
+ | | |
145
+ |---|---|
146
+ | Start | [Introduction](docs/01-introduction.md) · [Quickstart](docs/02-quickstart.md) · [Tutorial](docs/03-tutorial.md) |
147
+ | Language | [`.pyweb` files](docs/04-pyweb-files.md) · [State & reactivity](docs/05-reactivity.md) · [Python in the browser](docs/07-browser-python.md) |
148
+ | Server | [Server functions & RPC](docs/06-server-functions.md) · [Pages & routing](docs/08-pages-routing-assets.md) · [Data](docs/09-data.md) · [Auth](docs/10-auth.md) |
149
+ | Ship | [Testing](docs/11-testing.md) · [Deployment](docs/12-deployment.md) · [Security](docs/13-security.md) · [CLI](docs/14-cli.md) |
150
+ | Reference | [Toolkit & stability](docs/15-toolkit.md) · [Limitations & roadmap](docs/16-limitations-roadmap.md) · [Architecture](ARCHITECTURE.md) · [Changelog](CHANGELOG.md) |
151
+
152
+ The same docs are published at
153
+ **[maanavkrishna.github.io/PyWeb](https://maanavkrishna.github.io/PyWeb/)**,
154
+ with compiler output shown next to each example.
155
+
156
+ ## Examples
157
+
158
+ Each runs with `pyweb dev examples/<name>/app.pyweb` and is exercised in
159
+ a real browser by the test suite.
160
+
161
+ | Example | Shows |
162
+ |---|---|
163
+ | [`counter`](examples/counter/app.pyweb) | signals, computed values, binding a number input |
164
+ | [`todo`](examples/todo/app.pyweb) | components, list mutation, filters, keyed lists |
165
+ | [`blog`](examples/blog/app.pyweb) | SQL database, server functions, route params, 404s, validation errors |
166
+ | [`auth`](examples/auth/app.pyweb) | registration, password hashing, sessions, protected pages |
167
+ | [`chat`](examples/chat/app.pyweb) | route params, shared server state, polling with `on_mount` |
168
+ | [`showcase`](examples/showcase/app.pyweb) | everything on one page, with a stylesheet |
169
+
170
+ ## Command line
171
+
172
+ ```bash
173
+ pyweb new myapp # scaffold
174
+ pyweb dev app.pyweb # dev server: live reload + error overlay
175
+ pyweb inspect app.pyweb # where each name runs, and why
176
+ pyweb check app.pyweb # compile + security checks for CI
177
+ pyweb build app.pyweb --out dist --production # self-contained, hashed, minified dist/
178
+ pyweb serve dist # production server (/healthz, CSP, graceful shutdown)
179
+ ```
180
+
181
+ ## Status
182
+
183
+ PyWeb 0.1 is the first public release (beta). The language, server API,
184
+ RPC protocol and CLI are documented and tested, and changes to them are
185
+ announced in the changelog (see [stability](docs/15-toolkit.md#stability)). The test suite covers the
186
+ parser, the Python→JavaScript translation (differentially, against
187
+ CPython), the reactive runtime, server rendering, RPC, sessions, every
188
+ example app in Chromium, and the database/Redis layers against real
189
+ Postgres, MySQL and Redis servers, on Python 3.10–3.13.
190
+
191
+ ## Authors
192
+
193
+ Built by [MaanavKrishna](https://github.com/MaanavKrishna) and Claude.
194
+
195
+ ## Contributing
196
+
197
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Security reports:
198
+ [SECURITY.md](SECURITY.md). License: MIT.
@@ -0,0 +1,145 @@
1
+ # PyWeb
2
+
3
+ **Full-stack web apps in one Python file.** Server-rendered pages,
4
+ reactive browser UI compiled from Python, and typed calls to server
5
+ functions, without a JavaScript toolchain.
6
+
7
+ [![CI](https://github.com/MaanavKrishna/PyWeb/actions/workflows/ci.yml/badge.svg)](https://github.com/MaanavKrishna/PyWeb/actions/workflows/ci.yml)
8
+ [![Docs](https://img.shields.io/badge/docs-maanavkrishna.github.io%2FPyWeb-0d9488)](https://maanavkrishna.github.io/PyWeb/)
9
+ ![Python](https://img.shields.io/badge/python-3.10%E2%80%933.13-3776ab)
10
+ ![PyPI](https://img.shields.io/pypi/v/pyweb-stack)
11
+ ![License](https://img.shields.io/badge/license-MIT-lightgrey)
12
+
13
+ ```pyweb
14
+ from pyweb import App, server
15
+
16
+ app = App(title="Guestbook")
17
+ ENTRIES = []
18
+
19
+
20
+ @server
21
+ def sign(name: str) -> list:
22
+ ENTRIES.append(name.strip() or "anonymous")
23
+ return ENTRIES
24
+
25
+
26
+ @app.page("/")
27
+ def Home():
28
+ entries = list(ENTRIES) # computed on the server per request
29
+ name = "" # bound to the input: browser state
30
+
31
+ def submit(): # compiled to JavaScript
32
+ entries = sign(name) # typed RPC to the server
33
+ name = ""
34
+
35
+ <main>
36
+ <h1>Guestbook ({len(entries)})</h1>
37
+ <form onsubmit={submit}>
38
+ <input bind={name} placeholder="Your name" />
39
+ <button>Sign</button>
40
+ </form>
41
+ <ul>
42
+ for entry in entries:
43
+ <li>{entry}</li>
44
+ </ul>
45
+ </main>
46
+ ```
47
+
48
+ ```bash
49
+ pip install pyweb-stack # imported as `pyweb`
50
+ pyweb dev app.pyweb # http://localhost:8000
51
+ ```
52
+
53
+ ## Why PyWeb
54
+
55
+ - **Interactions run in the browser.** Handlers and expressions are
56
+ compiled to small JavaScript modules; the server is only contacted when
57
+ your code calls a `@server` function. No WebSocket per user, no
58
+ multi-megabyte Python runtime in the browser.
59
+ - **Every page is server-rendered.** Complete HTML on first paint, real
60
+ data from your database, good for SEO and slow devices.
61
+ - **You never write an API layer.** `@server` functions get endpoints,
62
+ argument validation, typed errors and generated browser calls.
63
+ - **Plain variables are state.** The compiler sees which variables your
64
+ handlers change and makes exactly those reactive; updates touch only
65
+ the DOM nodes that read them.
66
+ - **Boundaries are checked.** Database handles, imports and secrets can't
67
+ leak into browser code: it's a compile error with a line number.
68
+ `pyweb inspect` explains where every name runs and why.
69
+ - **Stateless servers.** Signed-cookie sessions and plain HTTP RPC scale
70
+ horizontally behind any load balancer. Deploy with `pyweb serve`,
71
+ uvicorn/gunicorn (ASGI) or the generated Dockerfile.
72
+
73
+ A typical interactive page ships under 1 KB of page code plus a ~10 KB
74
+ (gzip) runtime that's cached across pages. Pages without interactivity
75
+ ship no JavaScript.
76
+
77
+ ## Is it for you?
78
+
79
+ **Good fit:** internal tools, admin panels, dashboards, CRUD apps,
80
+ small SaaS products and content sites with interactive parts, built by
81
+ people who'd rather stay in Python.
82
+
83
+ **Not a fit:** large client-heavy single-page apps that need the npm
84
+ ecosystem (use React/Svelte/Vue), or running scientific Python in the
85
+ browser (use Pyodide/PyScript). See the
86
+ [comparison](https://maanavkrishna.github.io/PyWeb/introduction.html)
87
+ and [current limitations](docs/16-limitations-roadmap.md).
88
+
89
+ ## Documentation
90
+
91
+ | | |
92
+ |---|---|
93
+ | Start | [Introduction](docs/01-introduction.md) · [Quickstart](docs/02-quickstart.md) · [Tutorial](docs/03-tutorial.md) |
94
+ | Language | [`.pyweb` files](docs/04-pyweb-files.md) · [State & reactivity](docs/05-reactivity.md) · [Python in the browser](docs/07-browser-python.md) |
95
+ | Server | [Server functions & RPC](docs/06-server-functions.md) · [Pages & routing](docs/08-pages-routing-assets.md) · [Data](docs/09-data.md) · [Auth](docs/10-auth.md) |
96
+ | Ship | [Testing](docs/11-testing.md) · [Deployment](docs/12-deployment.md) · [Security](docs/13-security.md) · [CLI](docs/14-cli.md) |
97
+ | Reference | [Toolkit & stability](docs/15-toolkit.md) · [Limitations & roadmap](docs/16-limitations-roadmap.md) · [Architecture](ARCHITECTURE.md) · [Changelog](CHANGELOG.md) |
98
+
99
+ The same docs are published at
100
+ **[maanavkrishna.github.io/PyWeb](https://maanavkrishna.github.io/PyWeb/)**,
101
+ with compiler output shown next to each example.
102
+
103
+ ## Examples
104
+
105
+ Each runs with `pyweb dev examples/<name>/app.pyweb` and is exercised in
106
+ a real browser by the test suite.
107
+
108
+ | Example | Shows |
109
+ |---|---|
110
+ | [`counter`](examples/counter/app.pyweb) | signals, computed values, binding a number input |
111
+ | [`todo`](examples/todo/app.pyweb) | components, list mutation, filters, keyed lists |
112
+ | [`blog`](examples/blog/app.pyweb) | SQL database, server functions, route params, 404s, validation errors |
113
+ | [`auth`](examples/auth/app.pyweb) | registration, password hashing, sessions, protected pages |
114
+ | [`chat`](examples/chat/app.pyweb) | route params, shared server state, polling with `on_mount` |
115
+ | [`showcase`](examples/showcase/app.pyweb) | everything on one page, with a stylesheet |
116
+
117
+ ## Command line
118
+
119
+ ```bash
120
+ pyweb new myapp # scaffold
121
+ pyweb dev app.pyweb # dev server: live reload + error overlay
122
+ pyweb inspect app.pyweb # where each name runs, and why
123
+ pyweb check app.pyweb # compile + security checks for CI
124
+ pyweb build app.pyweb --out dist --production # self-contained, hashed, minified dist/
125
+ pyweb serve dist # production server (/healthz, CSP, graceful shutdown)
126
+ ```
127
+
128
+ ## Status
129
+
130
+ PyWeb 0.1 is the first public release (beta). The language, server API,
131
+ RPC protocol and CLI are documented and tested, and changes to them are
132
+ announced in the changelog (see [stability](docs/15-toolkit.md#stability)). The test suite covers the
133
+ parser, the Python→JavaScript translation (differentially, against
134
+ CPython), the reactive runtime, server rendering, RPC, sessions, every
135
+ example app in Chromium, and the database/Redis layers against real
136
+ Postgres, MySQL and Redis servers, on Python 3.10–3.13.
137
+
138
+ ## Authors
139
+
140
+ Built by [MaanavKrishna](https://github.com/MaanavKrishna) and Claude.
141
+
142
+ ## Contributing
143
+
144
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Security reports:
145
+ [SECURITY.md](SECURITY.md). License: MIT.
@@ -0,0 +1,72 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pyweb-stack"
7
+ version = "0.1.0"
8
+ description = "Full-stack web apps in one Python file: server-rendered pages, reactive browser UI, typed RPC."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ dependencies = []
13
+ authors = [
14
+ { name = "MaanavKrishna", email = "67054795+MaanavKrishna@users.noreply.github.com" },
15
+ { name = "Claude", email = "noreply@anthropic.com" },
16
+ ]
17
+ maintainers = [{ name = "MaanavKrishna", email = "67054795+MaanavKrishna@users.noreply.github.com" }]
18
+ keywords = ["web", "framework", "full-stack", "reactive", "rpc", "ssr", "compiler"]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Environment :: Web Environment",
22
+ "Framework :: AsyncIO",
23
+ "Intended Audience :: Developers",
24
+ "License :: OSI Approved :: MIT License",
25
+ "Operating System :: OS Independent",
26
+ "Programming Language :: JavaScript",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3 :: Only",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Programming Language :: Python :: 3.13",
33
+ "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
34
+ "Topic :: Software Development :: Compilers",
35
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
36
+ ]
37
+
38
+ [project.optional-dependencies]
39
+ postgres = ["psycopg[binary]>=3.1"]
40
+ mysql = ["PyMySQL>=1.1"]
41
+ redis = ["redis>=5"]
42
+ crypto = ["cryptography>=42"]
43
+ asgi = ["uvicorn>=0.30"]
44
+ all = ["psycopg[binary]>=3.1", "PyMySQL>=1.1", "redis>=5", "cryptography>=42", "uvicorn>=0.30"]
45
+ test = ["pytest>=8", "playwright>=1.45"]
46
+
47
+ [project.urls]
48
+ Homepage = "https://maanavkrishna.github.io/PyWeb/"
49
+ Documentation = "https://maanavkrishna.github.io/PyWeb/guide.html"
50
+ Source = "https://github.com/MaanavKrishna/PyWeb"
51
+ Changelog = "https://github.com/MaanavKrishna/PyWeb/blob/main/CHANGELOG.md"
52
+ Issues = "https://github.com/MaanavKrishna/PyWeb/issues"
53
+
54
+ [project.scripts]
55
+ pyweb = "pyweb.cli:main"
56
+
57
+ [tool.setuptools.packages.find]
58
+ include = ["pyweb*"]
59
+
60
+ [tool.setuptools.package-data]
61
+ pyweb = ["runtime/browser/*.js", "py.typed"]
62
+
63
+ [tool.pytest.ini_options]
64
+ testpaths = ["tests"]
65
+
66
+ [tool.ruff]
67
+ line-length = 120
68
+ target-version = "py310"
69
+ extend-exclude = ["website/dist"]
70
+
71
+ [tool.ruff.lint]
72
+ select = ["E9", "F"]
@@ -0,0 +1,71 @@
1
+ """PyWeb — Python from browser to database."""
2
+
3
+ from .app import App
4
+ from .models import Email, Model
5
+ from . import auth, cache, jobs, realtime, security, observability, forms, testing
6
+ from . import sync, deploy, uploads, lsp, live
7
+ from . import browser as browser_api
8
+ from . import build, css, plugins, platform
9
+ from .jobs import task
10
+ from .context import NotFound, redirect, request, session
11
+ from .rpc import RPCError
12
+ from .decorators import component, edge, server, worker
13
+
14
+ __all__ = [
15
+ "App",
16
+ "request",
17
+ "session",
18
+ "redirect",
19
+ "NotFound",
20
+ "RPCError",
21
+ "component",
22
+ "edge",
23
+ "server",
24
+ "worker",
25
+ "Email",
26
+ "Model",
27
+ "task",
28
+ "auth",
29
+ "cache",
30
+ "jobs",
31
+ "realtime",
32
+ "security",
33
+ "observability",
34
+ "forms",
35
+ "testing",
36
+ "sync",
37
+ "live",
38
+ "deploy",
39
+ "uploads",
40
+ "lsp",
41
+ "browser_api",
42
+ "build",
43
+ "css",
44
+ "plugins",
45
+ "platform",
46
+ ]
47
+
48
+ def _version():
49
+ """Single source of truth: installed dist metadata, else pyproject."""
50
+ try:
51
+ from importlib.metadata import version, PackageNotFoundError
52
+ try:
53
+ return version("pyweb-stack")
54
+ except PackageNotFoundError:
55
+ pass
56
+ except ImportError: # pragma: no cover - ancient Python
57
+ pass
58
+ import os
59
+ import re
60
+ root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
61
+ try:
62
+ with open(os.path.join(root, "pyproject.toml")) as fh:
63
+ m = re.search(r'^version\s*=\s*"([^"]+)"', fh.read(), re.M)
64
+ if m:
65
+ return m.group(1)
66
+ except OSError:
67
+ pass
68
+ return "0.0.0+unknown"
69
+
70
+
71
+ __version__ = _version()
@@ -0,0 +1,37 @@
1
+ """Application object: page registry and configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class App:
7
+ def __init__(self, database=None, cache=None, auth=None, plugins=(), *,
8
+ title="PyWeb", stylesheets=(), lang="en"):
9
+ self.title = title
10
+ self.stylesheets = list(stylesheets)
11
+ self.lang = lang
12
+ self.database = database
13
+ self.cache = cache
14
+ self.auth = auth
15
+ self.pages: list[tuple[str, str, dict]] = []
16
+ from pyweb.plugins import Registry
17
+ self.plugins = Registry()
18
+ for plugin in plugins:
19
+ self.use(plugin)
20
+
21
+ def page(self, route, *, title=None, render="server"):
22
+ def deco(fn):
23
+ self.pages.append((route, fn.__name__, {"render": render, "title": title}))
24
+ fn.__pyweb_route__ = route
25
+ fn.__pyweb_title__ = title
26
+ fn.__pyweb_render__ = render
27
+ return fn
28
+
29
+ return deco
30
+
31
+ def use(self, plugin):
32
+ """Attach a :class:`pyweb.plugins.Plugin`; merges its routes."""
33
+ self.plugins.add(plugin)
34
+ for route, fn in plugin.routes:
35
+ self.pages.append((route, fn.__name__, {"render": "server",
36
+ "plugin": plugin.name}))
37
+ return plugin
@@ -0,0 +1,139 @@
1
+ """Load a `.pyweb` app for serving: compile, execute the Python half,
2
+ wire ``@server`` functions to RPC, and render pages per request.
3
+
4
+ ``pyweb dev``, ``pyweb serve`` and the ASGI adapter all go through
5
+ :class:`LoadedApp`, so the three behave identically.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import ast
11
+ import copy
12
+ import inspect
13
+ import os
14
+ import sys
15
+ import types
16
+
17
+ from .compiler import compile_source
18
+ from .compiler import parser as P
19
+ from .compiler.lower import is_ui_stmt
20
+ from .context import NotFound, Redirect
21
+ from .ssr import Renderer, page_html
22
+
23
+ STATE_PREFIX = "__pyweb_state_"
24
+
25
+
26
+ def _state_function(fn):
27
+ """Copy a page/component function into one that returns its locals.
28
+
29
+ Markup placeholders are dropped; an explicit ``return`` (e.g.
30
+ ``return redirect("/login")``) still returns early.
31
+ """
32
+ new = copy.deepcopy(fn)
33
+ new.name = STATE_PREFIX + fn.name
34
+ new.decorator_list = []
35
+ body = [s for s in new.body if not is_ui_stmt(s)]
36
+ ret = ast.Return(value=ast.Call(func=ast.Name(id="locals", ctx=ast.Load()), args=[], keywords=[]))
37
+ body.append(ret)
38
+ new.body = body
39
+ ast.fix_missing_locations(new)
40
+ return new
41
+
42
+
43
+ class LoadedApp:
44
+ def __init__(self, path=None, *, source=None, filename=None, asset_urls=None,
45
+ stylesheets=None):
46
+ if source is None:
47
+ with open(path, encoding="utf-8") as fh:
48
+ source = fh.read()
49
+ self.path = path
50
+ self.filename = filename or path or "app.pyweb"
51
+ self.source = source
52
+ self.asset_urls = dict(asset_urls or {})
53
+ self.extra_stylesheets = list(stylesheets or [])
54
+ self.compiled = compile_source(source, filename=self.filename)
55
+ self.ctx = self.compiled["context"]
56
+ self.module = self._exec()
57
+ self.rpc = {}
58
+ for spec in self.compiled["rpc"]:
59
+ fn = self.module.__dict__.get(spec["name"])
60
+ if callable(fn):
61
+ self.rpc[spec["name"]] = fn
62
+ app_obj = next((v for v in self.module.__dict__.values()
63
+ if type(v).__name__ == "App" and type(v).__module__.startswith("pyweb")), None)
64
+ self.app = app_obj
65
+ self.title = getattr(app_obj, "title", None) or "PyWeb"
66
+ self.stylesheets = list(getattr(app_obj, "stylesheets", []) or []) + self.extra_stylesheets
67
+ self.lang = getattr(app_obj, "lang", "en") or "en"
68
+
69
+ # ----------------------------------------------------------- module
70
+ def _exec(self):
71
+ py_source, _ui = P.split_sources(self.source)
72
+ tree = ast.parse(py_source, filename=self.filename)
73
+ infos = [p["info"] for p in self.compiled["pages"].values()]
74
+ infos += list(self.compiled["components"].values())
75
+ for info in infos:
76
+ tree.body.append(_state_function(info.node))
77
+ stem = os.path.splitext(os.path.basename(self.filename))[0] or "app"
78
+ mod = types.ModuleType(f"pyweb_app_{stem}")
79
+ mod.__file__ = os.path.abspath(self.filename) if self.path else self.filename
80
+ mod.__dict__["__pyweb_ui__"] = lambda *_a: None
81
+ app_dir = os.path.dirname(os.path.abspath(self.path)) if self.path else None
82
+ if app_dir and app_dir not in sys.path:
83
+ sys.path.insert(0, app_dir)
84
+ sys.modules[mod.__name__] = mod
85
+ exec(compile(tree, self.filename, "exec"), mod.__dict__) # noqa: S102 - the app itself
86
+ return mod
87
+
88
+ # -------------------------------------------------------- rendering
89
+ def _component_state(self, name, props):
90
+ info = self.compiled["components"][name]
91
+ fn = self.module.__dict__[STATE_PREFIX + name]
92
+ env = fn(**{k: v for k, v in props.items() if k in info.params})
93
+ return info.ui, env
94
+
95
+ def renderer(self):
96
+ return Renderer(self.module.__dict__, component_state=self._component_state, strict=True)
97
+
98
+ def coerce_params(self, page_name, raw):
99
+ fn = self.module.__dict__[STATE_PREFIX + page_name]
100
+ out = {}
101
+ for pname, param in inspect.signature(fn).parameters.items():
102
+ if pname not in raw:
103
+ continue
104
+ ann = param.annotation
105
+ value = raw[pname]
106
+ try:
107
+ if ann in (int, "int"):
108
+ value = int(value)
109
+ elif ann in (float, "float"):
110
+ value = float(value)
111
+ except ValueError:
112
+ raise NotFound(f"{pname}={value!r}") from None
113
+ out[pname] = value
114
+ return out
115
+
116
+ def render(self, page_name, params=None):
117
+ """Return ``html`` (str) or a :class:`~pyweb.context.Redirect`."""
118
+ page = self.compiled["pages"][page_name]
119
+ info = page["info"]
120
+ fn = self.module.__dict__[STATE_PREFIX + page_name]
121
+ args = self.coerce_params(page_name, params or {})
122
+ result = fn(**args)
123
+ if isinstance(result, Redirect):
124
+ return result
125
+ if not isinstance(result, dict):
126
+ raise TypeError(f"page {page_name}() returned {type(result).__name__}; pages may only "
127
+ "return redirect(...)")
128
+ env = dict(result)
129
+ body = self.renderer().render(info.ui, env)
130
+ state = {k: env.get(k) for k in info.sent}
131
+ js_url = self.asset_urls.get(page_name)
132
+ if js_url is None and page["js"]:
133
+ js_url = page["js_url"]
134
+ title = info.title or self.title
135
+ if callable(getattr(self.app, "title_for", None)):
136
+ title = self.app.title_for(page_name) or title
137
+ return page_html(name=page_name, title=title, body=body, state=state,
138
+ js_url=js_url if page["js"] else None, css_urls=self.stylesheets,
139
+ lang=self.lang)