pyview-web 0.7.2__tar.gz → 0.8.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.
- {pyview_web-0.7.2 → pyview_web-0.8.0}/PKG-INFO +38 -34
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyproject.toml +56 -32
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/__init__.py +8 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/assets/package-lock.json +16 -14
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/assets/package.json +2 -2
- pyview_web-0.8.0/pyview/binding/__init__.py +47 -0
- pyview_web-0.8.0/pyview/binding/binder.py +134 -0
- pyview_web-0.8.0/pyview/binding/context.py +33 -0
- pyview_web-0.8.0/pyview/binding/converters.py +191 -0
- pyview_web-0.8.0/pyview/binding/helpers.py +78 -0
- pyview_web-0.8.0/pyview/binding/injectables.py +119 -0
- pyview_web-0.8.0/pyview/binding/params.py +105 -0
- pyview_web-0.8.0/pyview/binding/result.py +32 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/changesets/changesets.py +5 -1
- pyview_web-0.8.0/pyview/components/__init__.py +72 -0
- pyview_web-0.8.0/pyview/components/base.py +212 -0
- pyview_web-0.8.0/pyview/components/lifecycle.py +85 -0
- pyview_web-0.8.0/pyview/components/manager.py +366 -0
- pyview_web-0.8.0/pyview/components/renderer.py +14 -0
- pyview_web-0.8.0/pyview/components/slots.py +73 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/events/BaseEventHandler.py +27 -4
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/live_socket.py +70 -4
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/live_view.py +14 -2
- pyview_web-0.8.0/pyview/meta.py +18 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/phx_message.py +2 -2
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/pyview.py +10 -3
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/static/assets/app.js +2117 -817
- pyview_web-0.8.0/pyview/stream.py +308 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/template/live_template.py +4 -2
- pyview_web-0.8.0/pyview/template/live_view_template.py +338 -0
- pyview_web-0.8.0/pyview/template/render_diff.py +65 -0
- pyview_web-0.8.0/pyview/template/template_view.py +170 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/ibis/nodes.py +13 -1
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/ibis/tree.py +62 -3
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/ws_handler.py +50 -19
- {pyview_web-0.7.2 → pyview_web-0.8.0}/readme.md +13 -7
- pyview_web-0.7.2/LICENSE +0 -21
- pyview_web-0.7.2/pyview/meta.py +0 -6
- pyview_web-0.7.2/pyview/template/live_view_template.py +0 -158
- pyview_web-0.7.2/pyview/template/render_diff.py +0 -35
- pyview_web-0.7.2/pyview/template/template_view.py +0 -120
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/assets/js/app.js +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/assets/js/uploaders.js +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/async_stream_runner.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/auth/__init__.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/auth/provider.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/auth/required.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/changesets/__init__.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/cli/__init__.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/cli/commands/__init__.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/cli/commands/create_view.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/cli/main.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/csrf.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/events/AutoEventDispatch.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/events/__init__.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/events/info_event.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/instrumentation/__init__.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/instrumentation/interfaces.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/instrumentation/noop.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/js.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/live_routes.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/playground/__init__.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/playground/builder.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/playground/favicon.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/secret.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/session.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/static/assets/uploaders.js +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/template/__init__.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/template/context_processor.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/template/root_template.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/template/serializer.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/template/utils.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/uploads.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/__init__.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/flet/pubsub/__init__.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/flet/pubsub/pub_sub.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/ibis/__init__.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/ibis/compiler.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/ibis/context.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/ibis/errors.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/ibis/filters.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/ibis/loaders.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/ibis/template.py +0 -0
- {pyview_web-0.7.2 → pyview_web-0.8.0}/pyview/vendor/ibis/utils.py +0 -0
|
@@ -1,44 +1,43 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: pyview-web
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: LiveView in Python
|
|
5
|
-
License: MIT
|
|
6
|
-
License-File: LICENSE
|
|
7
5
|
Keywords: web,api,LiveView
|
|
8
6
|
Author: Larry Ogrodnek
|
|
9
|
-
Author-email: ogrodnek@gmail.com
|
|
10
|
-
|
|
7
|
+
Author-email: Larry Ogrodnek <ogrodnek@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Intended Audience :: Information Technology
|
|
10
|
+
Classifier: Intended Audience :: System Administrators
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Topic :: Internet
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Classifier: Topic :: Software Development
|
|
19
|
+
Classifier: Typing :: Typed
|
|
11
20
|
Classifier: Development Status :: 4 - Beta
|
|
12
21
|
Classifier: Environment :: Web Environment
|
|
13
22
|
Classifier: Framework :: AsyncIO
|
|
14
23
|
Classifier: Framework :: Pydantic
|
|
15
24
|
Classifier: Intended Audience :: Developers
|
|
16
|
-
Classifier: Intended Audience :: Information Technology
|
|
17
|
-
Classifier: Intended Audience :: System Administrators
|
|
18
25
|
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
-
Classifier:
|
|
20
|
-
Classifier: Programming Language :: Python
|
|
21
|
-
Classifier: Programming Language :: Python :: 3
|
|
26
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
27
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
28
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
29
|
Classifier: Programming Language :: Python :: 3.13
|
|
25
30
|
Classifier: Programming Language :: Python :: 3.14
|
|
26
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
27
|
-
Classifier: Topic :: Internet
|
|
28
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
|
29
31
|
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
30
|
-
Classifier: Topic ::
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Requires-Dist:
|
|
36
|
-
Requires-Dist:
|
|
37
|
-
Requires-Dist:
|
|
38
|
-
Requires-
|
|
39
|
-
Requires-Dist: pydantic (>=2.11,<3.0)
|
|
40
|
-
Requires-Dist: starlette (>=0.50.0,<0.51.0)
|
|
41
|
-
Requires-Dist: wsproto (>=1.3.0,<2.0.0)
|
|
32
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
33
|
+
Requires-Dist: starlette>=0.50.0,<0.51
|
|
34
|
+
Requires-Dist: wsproto>=1.3.0,<2
|
|
35
|
+
Requires-Dist: apscheduler>=3.11.0,<4
|
|
36
|
+
Requires-Dist: markupsafe>=3.0.2,<4
|
|
37
|
+
Requires-Dist: itsdangerous>=2.2.0,<3
|
|
38
|
+
Requires-Dist: pydantic>=2.11,<3
|
|
39
|
+
Requires-Dist: click>=8.1.7,<9
|
|
40
|
+
Requires-Python: >=3.11, <3.15
|
|
42
41
|
Project-URL: Homepage, https://pyview.rocks
|
|
43
42
|
Project-URL: Repository, https://github.com/ogrodnek/pyview
|
|
44
43
|
Description-Content-Type: text/markdown
|
|
@@ -51,6 +50,8 @@ Description-Content-Type: text/markdown
|
|
|
51
50
|
|
|
52
51
|
PyView enables dynamic, real-time web apps, using server-rendered HTML.
|
|
53
52
|
|
|
53
|
+
**Documentation**: <a href="https://pyview.rocks" target="_blank">https://pyview.rocks</a>
|
|
54
|
+
|
|
54
55
|
**Source Code**: <a href="https://github.com/ogrodnek/pyview" target="_blank">https://github.com/ogrodnek/pyview</a>
|
|
55
56
|
|
|
56
57
|
# Installation
|
|
@@ -136,28 +137,31 @@ PyView is in the very early stages of active development. Please check it out an
|
|
|
136
137
|
## Setup
|
|
137
138
|
|
|
138
139
|
```
|
|
139
|
-
|
|
140
|
+
uv sync
|
|
140
141
|
```
|
|
141
142
|
|
|
142
143
|
## Running
|
|
143
144
|
|
|
144
145
|
```
|
|
145
|
-
|
|
146
|
+
uv run uvicorn examples.app:app --reload
|
|
146
147
|
```
|
|
147
148
|
|
|
148
149
|
Then go to http://localhost:8000/
|
|
149
150
|
|
|
150
|
-
###
|
|
151
|
+
### uv Install
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
brew install uv
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
or
|
|
151
158
|
|
|
152
159
|
```
|
|
153
|
-
|
|
154
|
-
pipx install poetry
|
|
155
|
-
pipx ensurepath
|
|
160
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
156
161
|
```
|
|
157
162
|
|
|
158
|
-
(see https://
|
|
163
|
+
(see https://docs.astral.sh/uv/getting-started/installation/ for more details)
|
|
159
164
|
|
|
160
165
|
# License
|
|
161
166
|
|
|
162
167
|
PyView is licensed under the [MIT License](LICENSE).
|
|
163
|
-
|
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
[
|
|
1
|
+
[project]
|
|
2
2
|
name = "pyview-web"
|
|
3
|
-
|
|
4
|
-
packages = [
|
|
5
|
-
{ include = "pyview" },
|
|
6
|
-
]
|
|
7
|
-
|
|
8
|
-
version = "0.7.2"
|
|
3
|
+
version = "0.8.0"
|
|
9
4
|
description = "LiveView in Python"
|
|
10
|
-
authors = ["Larry Ogrodnek
|
|
11
|
-
license = "MIT"
|
|
5
|
+
authors = [{ name = "Larry Ogrodnek", email = "ogrodnek@gmail.com" }]
|
|
6
|
+
license = { text = "MIT" }
|
|
12
7
|
readme = "readme.md"
|
|
13
|
-
|
|
14
|
-
repository = "https://github.com/ogrodnek/pyview"
|
|
8
|
+
requires-python = ">=3.11,<3.15"
|
|
15
9
|
keywords = ["web", "api", "LiveView"]
|
|
16
10
|
|
|
17
11
|
classifiers = [
|
|
@@ -41,33 +35,49 @@ classifiers = [
|
|
|
41
35
|
"Topic :: Internet :: WWW/HTTP",
|
|
42
36
|
]
|
|
43
37
|
|
|
44
|
-
[
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
38
|
+
dependencies = [
|
|
39
|
+
"starlette>=0.50.0,<0.51",
|
|
40
|
+
"wsproto>=1.3.0,<2",
|
|
41
|
+
"APScheduler>=3.11.0,<4",
|
|
42
|
+
"markupsafe>=3.0.2,<4",
|
|
43
|
+
"itsdangerous>=2.2.0,<3",
|
|
44
|
+
"pydantic>=2.11,<3",
|
|
45
|
+
"click>=8.1.7,<9",
|
|
46
|
+
]
|
|
53
47
|
|
|
54
|
-
[
|
|
48
|
+
[project.scripts]
|
|
55
49
|
pv = "pyview.cli.main:cli"
|
|
56
50
|
|
|
57
|
-
[
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
[project.urls]
|
|
52
|
+
Homepage = "https://pyview.rocks"
|
|
53
|
+
Repository = "https://github.com/ogrodnek/pyview"
|
|
54
|
+
|
|
55
|
+
[dependency-groups]
|
|
56
|
+
dev = [
|
|
57
|
+
"pytest>=8.2.0,<9",
|
|
58
|
+
"ruff>=0.14.0",
|
|
59
|
+
"pyright>=1.1.403,<2",
|
|
60
|
+
"pytest-cov>=6.1.1,<7",
|
|
61
|
+
"pytest-asyncio>=1.3.0,<2",
|
|
62
|
+
]
|
|
63
|
+
profiling = [
|
|
64
|
+
"scalene>=1.5.51,<2; python_version != '3.11.0'",
|
|
65
|
+
]
|
|
66
|
+
docs = [
|
|
67
|
+
"mkdocs>=1.6.1,<2",
|
|
68
|
+
"mkdocs-material>=9.6.14,<10",
|
|
69
|
+
]
|
|
63
70
|
|
|
64
|
-
[
|
|
65
|
-
|
|
71
|
+
[build-system]
|
|
72
|
+
requires = ["uv_build>=0.7,<1"]
|
|
73
|
+
build-backend = "uv_build"
|
|
66
74
|
|
|
75
|
+
[tool.uv]
|
|
76
|
+
package = true
|
|
67
77
|
|
|
68
|
-
[build-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
[tool.uv.build-backend]
|
|
79
|
+
module-root = "."
|
|
80
|
+
module-name = "pyview"
|
|
71
81
|
|
|
72
82
|
[tool.pyright]
|
|
73
83
|
exclude = [
|
|
@@ -78,8 +88,13 @@ exclude = [
|
|
|
78
88
|
"**/node_modules",
|
|
79
89
|
"**/__pycache__",
|
|
80
90
|
# T-string files require Python 3.14+
|
|
91
|
+
"pyview/components/renderer.py",
|
|
81
92
|
"tests/test_live_view_template.py",
|
|
82
93
|
"tests/test_template_view.py",
|
|
94
|
+
"tests/test_stream_tstring.py",
|
|
95
|
+
"tests/components/test_statics_sharing.py",
|
|
96
|
+
"tests/components/test_lifecycle.py",
|
|
97
|
+
"tests/components/test_slots.py",
|
|
83
98
|
]
|
|
84
99
|
|
|
85
100
|
[tool.pytest.ini_options]
|
|
@@ -93,9 +108,18 @@ exclude = [
|
|
|
93
108
|
"pyview/template/__init__.py",
|
|
94
109
|
"pyview/template/template_view.py",
|
|
95
110
|
"pyview/template/live_view_template.py",
|
|
111
|
+
"pyview/components/slots.py",
|
|
96
112
|
"examples/views/count/count_tstring.py",
|
|
113
|
+
"examples/views/streams/streams_tstring.py",
|
|
114
|
+
"examples/views/components/slots_demo.py",
|
|
115
|
+
"examples/views/components/stateful_demo.py",
|
|
116
|
+
"examples/views/components/stateless_demo.py",
|
|
97
117
|
"tests/test_live_view_template.py",
|
|
98
118
|
"tests/test_template_view.py",
|
|
119
|
+
"tests/test_stream_tstring.py",
|
|
120
|
+
"tests/components/test_slots.py",
|
|
121
|
+
"tests/components/test_statics_sharing.py",
|
|
122
|
+
"tests/components/test_lifecycle.py",
|
|
99
123
|
]
|
|
100
124
|
|
|
101
125
|
[tool.ruff.lint]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from pyview.components import ComponentMeta, ComponentsManager, ComponentSocket, LiveComponent
|
|
1
2
|
from pyview.js import JsCommand
|
|
2
3
|
from pyview.live_socket import (
|
|
3
4
|
ConnectedLiveViewSocket,
|
|
@@ -8,6 +9,7 @@ from pyview.live_socket import (
|
|
|
8
9
|
from pyview.live_view import LiveView
|
|
9
10
|
from pyview.playground import playground
|
|
10
11
|
from pyview.pyview import PyView, RootTemplate, RootTemplateContext, defaultRootTemplate
|
|
12
|
+
from pyview.stream import Stream
|
|
11
13
|
|
|
12
14
|
__all__ = [
|
|
13
15
|
"LiveView",
|
|
@@ -21,4 +23,10 @@ __all__ = [
|
|
|
21
23
|
"ConnectedLiveViewSocket",
|
|
22
24
|
"UnconnectedSocket",
|
|
23
25
|
"playground",
|
|
26
|
+
"Stream",
|
|
27
|
+
# Components
|
|
28
|
+
"LiveComponent",
|
|
29
|
+
"ComponentMeta",
|
|
30
|
+
"ComponentSocket",
|
|
31
|
+
"ComponentsManager",
|
|
24
32
|
]
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
"": {
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"nprogress": "^0.2.0",
|
|
9
|
-
"phoenix": "^1.7.0
|
|
9
|
+
"phoenix": "^1.7.0",
|
|
10
10
|
"phoenix_html": "^3.2.0",
|
|
11
|
-
"phoenix_live_view": "^0.
|
|
11
|
+
"phoenix_live_view": "^0.20.17"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"node_modules/nprogress": {
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
"integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA=="
|
|
18
18
|
},
|
|
19
19
|
"node_modules/phoenix": {
|
|
20
|
-
"version": "1.
|
|
21
|
-
"resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.
|
|
22
|
-
"integrity": "sha512-
|
|
20
|
+
"version": "1.8.3",
|
|
21
|
+
"resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.8.3.tgz",
|
|
22
|
+
"integrity": "sha512-5bMYQI30wl3erxbHnXMdt1xuQeRTeEOpQrakf3yqj/1HRHl7Gj4Cdk2NKXkUcCD5WpbxrilvZEMexM1VhWbnDg==",
|
|
23
|
+
"license": "MIT"
|
|
23
24
|
},
|
|
24
25
|
"node_modules/phoenix_html": {
|
|
25
26
|
"version": "3.2.0",
|
|
@@ -27,9 +28,10 @@
|
|
|
27
28
|
"integrity": "sha512-zv7PIZk0MPkF0ax8n465Q6w86+sGAy5cTem6KcbkUbdgxGc0y3WZmzkM2bSlYdSGbLEZfjXxos1G72xXsha6xA=="
|
|
28
29
|
},
|
|
29
30
|
"node_modules/phoenix_live_view": {
|
|
30
|
-
"version": "0.
|
|
31
|
-
"resolved": "https://registry.npmjs.org/phoenix_live_view/-/phoenix_live_view-0.
|
|
32
|
-
"integrity": "sha512-
|
|
31
|
+
"version": "0.20.17",
|
|
32
|
+
"resolved": "https://registry.npmjs.org/phoenix_live_view/-/phoenix_live_view-0.20.17.tgz",
|
|
33
|
+
"integrity": "sha512-qGT3Jtj2wUawOaMrE8NKXmkexfaUn6bx5PuPMxWMzYuyp6Qv9i4xRZ2T3U6avC5Kf+oJEiBVIiWrODooC0vpQw==",
|
|
34
|
+
"license": "MIT"
|
|
33
35
|
}
|
|
34
36
|
},
|
|
35
37
|
"dependencies": {
|
|
@@ -39,9 +41,9 @@
|
|
|
39
41
|
"integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA=="
|
|
40
42
|
},
|
|
41
43
|
"phoenix": {
|
|
42
|
-
"version": "1.
|
|
43
|
-
"resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.
|
|
44
|
-
"integrity": "sha512-
|
|
44
|
+
"version": "1.8.3",
|
|
45
|
+
"resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.8.3.tgz",
|
|
46
|
+
"integrity": "sha512-5bMYQI30wl3erxbHnXMdt1xuQeRTeEOpQrakf3yqj/1HRHl7Gj4Cdk2NKXkUcCD5WpbxrilvZEMexM1VhWbnDg=="
|
|
45
47
|
},
|
|
46
48
|
"phoenix_html": {
|
|
47
49
|
"version": "3.2.0",
|
|
@@ -49,9 +51,9 @@
|
|
|
49
51
|
"integrity": "sha512-zv7PIZk0MPkF0ax8n465Q6w86+sGAy5cTem6KcbkUbdgxGc0y3WZmzkM2bSlYdSGbLEZfjXxos1G72xXsha6xA=="
|
|
50
52
|
},
|
|
51
53
|
"phoenix_live_view": {
|
|
52
|
-
"version": "0.
|
|
53
|
-
"resolved": "https://registry.npmjs.org/phoenix_live_view/-/phoenix_live_view-0.
|
|
54
|
-
"integrity": "sha512-
|
|
54
|
+
"version": "0.20.17",
|
|
55
|
+
"resolved": "https://registry.npmjs.org/phoenix_live_view/-/phoenix_live_view-0.20.17.tgz",
|
|
56
|
+
"integrity": "sha512-qGT3Jtj2wUawOaMrE8NKXmkexfaUn6bx5PuPMxWMzYuyp6Qv9i4xRZ2T3U6avC5Kf+oJEiBVIiWrODooC0vpQw=="
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Parameter binding for pyview handlers.
|
|
2
|
+
|
|
3
|
+
This module provides signature-driven parameter binding, allowing handlers to
|
|
4
|
+
declare typed parameters that are automatically converted from request data.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
# Old style (manual extraction):
|
|
8
|
+
async def handle_params(self, url, params, socket):
|
|
9
|
+
page = int(params["page"][0]) if "page" in params else 1
|
|
10
|
+
|
|
11
|
+
# New style (typed binding):
|
|
12
|
+
async def handle_params(self, socket: LiveViewSocket[MyContext], page: int = 1):
|
|
13
|
+
# page is already an int!
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
Reserved parameter names (injected from context, not from URL params):
|
|
17
|
+
- socket: The LiveViewSocket instance
|
|
18
|
+
- url: The parsed URL
|
|
19
|
+
- event: The event name (for event handlers)
|
|
20
|
+
- payload: The event payload dict (for event handlers)
|
|
21
|
+
|
|
22
|
+
Type-based injection:
|
|
23
|
+
- params: Params -> injects Params container
|
|
24
|
+
- params: dict -> injects params as dict
|
|
25
|
+
- params: str -> treats "params" as a URL param name (not injected)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from .binder import Binder
|
|
29
|
+
from .context import BindContext
|
|
30
|
+
from .converters import ConversionError, ConverterRegistry
|
|
31
|
+
from .helpers import call_handle_event, call_handle_params
|
|
32
|
+
from .injectables import InjectableRegistry
|
|
33
|
+
from .params import Params
|
|
34
|
+
from .result import BindResult, ParamError
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"Params",
|
|
38
|
+
"BindContext",
|
|
39
|
+
"BindResult",
|
|
40
|
+
"ParamError",
|
|
41
|
+
"ConverterRegistry",
|
|
42
|
+
"ConversionError",
|
|
43
|
+
"InjectableRegistry",
|
|
44
|
+
"Binder",
|
|
45
|
+
"call_handle_event",
|
|
46
|
+
"call_handle_params",
|
|
47
|
+
]
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Core binder for signature-driven parameter binding."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
import inspect
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Callable, Generic, TypeVar, get_type_hints
|
|
9
|
+
|
|
10
|
+
from .context import BindContext
|
|
11
|
+
from .converters import ConversionError, ConverterRegistry
|
|
12
|
+
from .injectables import _NOT_FOUND, InjectableRegistry
|
|
13
|
+
from .result import BindResult, ParamError
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
T = TypeVar("T")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Binder(Generic[T]):
|
|
21
|
+
"""Binds function parameters from BindContext based on type annotations.
|
|
22
|
+
|
|
23
|
+
The binder inspects a function's signature and type hints, then:
|
|
24
|
+
1. Resolves injectable parameters (socket, event, payload, url, params)
|
|
25
|
+
2. Pulls values from params or payload for other parameters
|
|
26
|
+
3. Converts values to the expected types
|
|
27
|
+
4. Returns a BindResult with bound_args and any errors
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
converter: ConverterRegistry | None = None,
|
|
33
|
+
injectables: InjectableRegistry[T] | None = None,
|
|
34
|
+
):
|
|
35
|
+
self.converter = converter or ConverterRegistry()
|
|
36
|
+
self.injectables = injectables or InjectableRegistry()
|
|
37
|
+
|
|
38
|
+
def bind(self, func: Callable[..., Any], ctx: BindContext[T]) -> BindResult:
|
|
39
|
+
"""Bind context values to function signature.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
func: The function to bind parameters for
|
|
43
|
+
ctx: Binding context with params, payload, socket, etc.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
BindResult with bound_args dict and any errors
|
|
47
|
+
"""
|
|
48
|
+
sig = inspect.signature(func)
|
|
49
|
+
|
|
50
|
+
# Get type hints, falling back to empty for missing annotations
|
|
51
|
+
# NameError: forward reference can't be resolved
|
|
52
|
+
# AttributeError: accessing annotations on some objects
|
|
53
|
+
# RecursionError: circular type references
|
|
54
|
+
try:
|
|
55
|
+
hints = get_type_hints(func)
|
|
56
|
+
except (NameError, AttributeError, RecursionError) as e:
|
|
57
|
+
logger.debug("Could not resolve type hints for %s: %s", func.__name__, e)
|
|
58
|
+
hints = {}
|
|
59
|
+
|
|
60
|
+
bound: dict[str, Any] = {}
|
|
61
|
+
errors: list[ParamError] = []
|
|
62
|
+
|
|
63
|
+
for name, param in sig.parameters.items():
|
|
64
|
+
# Skip 'self' for methods
|
|
65
|
+
if name == "self":
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
# Skip *args and **kwargs (VAR_POSITIONAL and VAR_KEYWORD)
|
|
69
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
expected = hints.get(name, Any)
|
|
73
|
+
|
|
74
|
+
# 1) Try injectables first
|
|
75
|
+
injected = self.injectables.resolve(name, expected, ctx)
|
|
76
|
+
if injected is not _NOT_FOUND:
|
|
77
|
+
bound[name] = injected
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
# 2) Check for dataclass parameter - gather fields from params
|
|
81
|
+
if dataclasses.is_dataclass(expected) and isinstance(expected, type):
|
|
82
|
+
raw = self._resolve_dataclass_fields(expected, ctx)
|
|
83
|
+
try:
|
|
84
|
+
bound[name] = self.converter.convert(raw, expected)
|
|
85
|
+
except ConversionError as e:
|
|
86
|
+
errors.append(ParamError(name, repr(expected), raw, str(e)))
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# 3) Pull raw value from params or payload
|
|
90
|
+
raw = self._resolve_raw(name, ctx)
|
|
91
|
+
|
|
92
|
+
# 4) Handle missing values
|
|
93
|
+
if raw is None:
|
|
94
|
+
if param.default is not inspect.Parameter.empty:
|
|
95
|
+
bound[name] = param.default
|
|
96
|
+
continue
|
|
97
|
+
if self.converter.is_optional(expected):
|
|
98
|
+
bound[name] = None
|
|
99
|
+
continue
|
|
100
|
+
errors.append(ParamError(name, repr(expected), None, "missing required parameter"))
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# 5) Convert to expected type
|
|
104
|
+
try:
|
|
105
|
+
bound[name] = self.converter.convert(raw, expected)
|
|
106
|
+
except ConversionError as e:
|
|
107
|
+
errors.append(ParamError(name, repr(expected), raw, str(e)))
|
|
108
|
+
|
|
109
|
+
return BindResult(bound, errors)
|
|
110
|
+
|
|
111
|
+
def _resolve_dataclass_fields(self, expected: type, ctx: BindContext[T]) -> dict[str, Any]:
|
|
112
|
+
"""Gather dataclass fields from params."""
|
|
113
|
+
fields = dataclasses.fields(expected)
|
|
114
|
+
result: dict[str, Any] = {}
|
|
115
|
+
|
|
116
|
+
for field in fields:
|
|
117
|
+
if ctx.params.has(field.name):
|
|
118
|
+
result[field.name] = ctx.params.getlist(field.name)
|
|
119
|
+
elif ctx.payload and field.name in ctx.payload:
|
|
120
|
+
result[field.name] = ctx.payload[field.name]
|
|
121
|
+
|
|
122
|
+
return result
|
|
123
|
+
|
|
124
|
+
def _resolve_raw(self, name: str, ctx: BindContext[T]) -> Any | None:
|
|
125
|
+
"""Resolve raw value from params or payload."""
|
|
126
|
+
# Check params first
|
|
127
|
+
if ctx.params.has(name):
|
|
128
|
+
return ctx.params.getlist(name)
|
|
129
|
+
|
|
130
|
+
# Check payload
|
|
131
|
+
if ctx.payload and name in ctx.payload:
|
|
132
|
+
return ctx.payload[name]
|
|
133
|
+
|
|
134
|
+
return None
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Binding context for parameter resolution."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar
|
|
5
|
+
from urllib.parse import ParseResult
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from pyview.live_socket import LiveViewSocket
|
|
9
|
+
|
|
10
|
+
from .params import Params
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class BindContext(Generic[T]):
|
|
17
|
+
"""Context provided to the binder for resolving parameter values.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
params: Multi-value parameter container (query/path/form merged)
|
|
21
|
+
payload: Event payload dict (for handle_event)
|
|
22
|
+
url: Parsed URL (for handle_params)
|
|
23
|
+
socket: LiveView socket instance
|
|
24
|
+
event: Event name (for handle_event)
|
|
25
|
+
extra: Additional injectable values
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
params: "Params"
|
|
29
|
+
payload: Optional[dict[str, Any]]
|
|
30
|
+
url: Optional[ParseResult]
|
|
31
|
+
socket: Optional["LiveViewSocket[T]"]
|
|
32
|
+
event: Optional[str]
|
|
33
|
+
extra: dict[str, Any] = field(default_factory=dict)
|