pyview-web 0.3.0__py3-none-any.whl → 0.4.2__py3-none-any.whl
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.
Potentially problematic release.
This version of pyview-web might be problematic. Click here for more details.
- pyview/live_socket.py +5 -4
- pyview/uploads.py +59 -8
- pyview/ws_handler.py +4 -0
- {pyview_web-0.3.0.dist-info → pyview_web-0.4.2.dist-info}/METADATA +6 -4
- {pyview_web-0.3.0.dist-info → pyview_web-0.4.2.dist-info}/RECORD +8 -8
- {pyview_web-0.3.0.dist-info → pyview_web-0.4.2.dist-info}/WHEEL +1 -1
- {pyview_web-0.3.0.dist-info → pyview_web-0.4.2.dist-info}/entry_points.txt +0 -0
- {pyview_web-0.3.0.dist-info → pyview_web-0.4.2.dist-info/licenses}/LICENSE +0 -0
pyview/live_socket.py
CHANGED
|
@@ -11,6 +11,7 @@ from typing import (
|
|
|
11
11
|
Union,
|
|
12
12
|
TypeAlias,
|
|
13
13
|
TypeGuard,
|
|
14
|
+
Callable,
|
|
14
15
|
)
|
|
15
16
|
from urllib.parse import urlencode
|
|
16
17
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
@@ -46,9 +47,9 @@ class UnconnectedSocket(Generic[T]):
|
|
|
46
47
|
connected: bool = False
|
|
47
48
|
|
|
48
49
|
def allow_upload(
|
|
49
|
-
self, upload_name: str, constraints: UploadConstraints
|
|
50
|
+
self, upload_name: str, constraints: UploadConstraints, auto_upload: bool = False, progress: Optional[Callable] = None
|
|
50
51
|
) -> UploadConfig:
|
|
51
|
-
return UploadConfig(name=upload_name, constraints=constraints)
|
|
52
|
+
return UploadConfig(name=upload_name, constraints=constraints, autoUpload=auto_upload, progress_callback=progress)
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
class ConnectedLiveViewSocket(Generic[T]):
|
|
@@ -196,9 +197,9 @@ class ConnectedLiveViewSocket(Generic[T]):
|
|
|
196
197
|
self.pending_events.append((event, value))
|
|
197
198
|
|
|
198
199
|
def allow_upload(
|
|
199
|
-
self, upload_name: str, constraints: UploadConstraints
|
|
200
|
+
self, upload_name: str, constraints: UploadConstraints, auto_upload: bool = False, progress: Optional[Callable] = None
|
|
200
201
|
) -> UploadConfig:
|
|
201
|
-
return self.upload_manager.allow_upload(upload_name, constraints)
|
|
202
|
+
return self.upload_manager.allow_upload(upload_name, constraints, auto_upload, progress)
|
|
202
203
|
|
|
203
204
|
async def close(self):
|
|
204
205
|
self.connected = False
|
pyview/uploads.py
CHANGED
|
@@ -2,7 +2,7 @@ import datetime
|
|
|
2
2
|
import uuid
|
|
3
3
|
import logging
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
|
-
from typing import Optional, Any, Literal, Generator
|
|
5
|
+
from typing import Optional, Any, Literal, Generator, Callable
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
from contextlib import contextmanager
|
|
8
8
|
import os
|
|
@@ -105,6 +105,7 @@ class UploadConfig(BaseModel):
|
|
|
105
105
|
errors: list[ConstraintViolation] = Field(default_factory=list)
|
|
106
106
|
autoUpload: bool = False
|
|
107
107
|
constraints: UploadConstraints = Field(default_factory=UploadConstraints)
|
|
108
|
+
progress_callback: Optional[Callable] = None
|
|
108
109
|
|
|
109
110
|
uploads: ActiveUploads = Field(default_factory=ActiveUploads)
|
|
110
111
|
|
|
@@ -135,8 +136,9 @@ class UploadConfig(BaseModel):
|
|
|
135
136
|
self.errors.append(ConstraintViolation(ref=self.ref, code="too_many_files"))
|
|
136
137
|
|
|
137
138
|
def update_progress(self, ref: str, progress: int):
|
|
138
|
-
self.entries_by_ref
|
|
139
|
-
|
|
139
|
+
if ref in self.entries_by_ref:
|
|
140
|
+
self.entries_by_ref[ref].progress = progress
|
|
141
|
+
self.entries_by_ref[ref].done = progress == 100
|
|
140
142
|
|
|
141
143
|
@contextmanager
|
|
142
144
|
def consume_uploads(self) -> Generator[list["ActiveUpload"], None, None]:
|
|
@@ -152,6 +154,34 @@ class UploadConfig(BaseModel):
|
|
|
152
154
|
self.uploads = ActiveUploads()
|
|
153
155
|
self.entries_by_ref = {}
|
|
154
156
|
|
|
157
|
+
@contextmanager
|
|
158
|
+
def consume_upload_entry(self, entry_ref: str) -> Generator[Optional["ActiveUpload"], None, None]:
|
|
159
|
+
"""Consume a single upload entry by its ref"""
|
|
160
|
+
upload = None
|
|
161
|
+
join_ref = None
|
|
162
|
+
|
|
163
|
+
# Find the join_ref for this entry
|
|
164
|
+
for jr, active_upload in self.uploads.uploads.items():
|
|
165
|
+
if active_upload.entry.ref == entry_ref:
|
|
166
|
+
upload = active_upload
|
|
167
|
+
join_ref = jr
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
yield upload
|
|
172
|
+
finally:
|
|
173
|
+
if upload and join_ref:
|
|
174
|
+
try:
|
|
175
|
+
upload.close()
|
|
176
|
+
except Exception:
|
|
177
|
+
logger.warning("Error closing upload entry", exc_info=True)
|
|
178
|
+
|
|
179
|
+
# Remove only this specific upload
|
|
180
|
+
if join_ref in self.uploads.uploads:
|
|
181
|
+
del self.uploads.uploads[join_ref]
|
|
182
|
+
if entry_ref in self.entries_by_ref:
|
|
183
|
+
del self.entries_by_ref[entry_ref]
|
|
184
|
+
|
|
155
185
|
def close(self):
|
|
156
186
|
self.uploads.close()
|
|
157
187
|
|
|
@@ -165,9 +195,9 @@ class UploadManager:
|
|
|
165
195
|
self.upload_config_join_refs = {}
|
|
166
196
|
|
|
167
197
|
def allow_upload(
|
|
168
|
-
self, upload_name: str, constraints: UploadConstraints
|
|
198
|
+
self, upload_name: str, constraints: UploadConstraints, auto_upload: bool = False, progress: Optional[Callable] = None
|
|
169
199
|
) -> UploadConfig:
|
|
170
|
-
config = UploadConfig(name=upload_name, constraints=constraints)
|
|
200
|
+
config = UploadConfig(name=upload_name, constraints=constraints, autoUpload=auto_upload, progress_callback=progress)
|
|
171
201
|
self.upload_configs[upload_name] = config
|
|
172
202
|
return config
|
|
173
203
|
|
|
@@ -242,13 +272,33 @@ class UploadManager:
|
|
|
242
272
|
config.update_progress(entry_ref, progress)
|
|
243
273
|
|
|
244
274
|
if progress == 100:
|
|
245
|
-
|
|
246
|
-
|
|
275
|
+
try:
|
|
276
|
+
joinRef_to_remove = config.uploads.join_ref_for_entry(entry_ref)
|
|
277
|
+
if joinRef_to_remove in self.upload_config_join_refs:
|
|
278
|
+
del self.upload_config_join_refs[joinRef_to_remove]
|
|
279
|
+
except (IndexError, KeyError):
|
|
280
|
+
# Entry might have already been consumed and removed
|
|
281
|
+
pass
|
|
247
282
|
|
|
248
283
|
def no_progress(self, joinRef) -> bool:
|
|
249
284
|
config = self.upload_config_join_refs[joinRef]
|
|
250
285
|
return config.uploads.no_progress()
|
|
251
286
|
|
|
287
|
+
async def trigger_progress_callback_if_exists(self, payload: dict[str, Any], socket):
|
|
288
|
+
"""Trigger progress callback if one exists for this upload config"""
|
|
289
|
+
upload_config_ref = payload["ref"]
|
|
290
|
+
config = self.config_for_ref(upload_config_ref)
|
|
291
|
+
|
|
292
|
+
if config and config.progress_callback:
|
|
293
|
+
entry_ref = payload["entry_ref"]
|
|
294
|
+
if entry_ref in config.entries_by_ref:
|
|
295
|
+
entry = config.entries_by_ref[entry_ref]
|
|
296
|
+
# Update entry progress before calling callback
|
|
297
|
+
progress = int(payload["progress"])
|
|
298
|
+
entry.progress = progress
|
|
299
|
+
entry.done = progress == 100
|
|
300
|
+
await config.progress_callback(entry, socket)
|
|
301
|
+
|
|
252
302
|
def close(self):
|
|
253
303
|
for config in self.upload_configs.values():
|
|
254
304
|
config.close()
|
|
@@ -272,6 +322,7 @@ def live_file_input(config: Optional[UploadConfig]) -> Markup:
|
|
|
272
322
|
accepted = ",".join(config.constraints.accept)
|
|
273
323
|
accept = f'accept="{accepted}"' if accepted else ""
|
|
274
324
|
multiple = "multiple" if config.constraints.max_files > 1 else ""
|
|
325
|
+
auto_upload = "data-phx-auto-upload" if config.autoUpload else ""
|
|
275
326
|
|
|
276
327
|
return Markup(
|
|
277
328
|
f"""
|
|
@@ -281,7 +332,7 @@ def live_file_input(config: Optional[UploadConfig]) -> Markup:
|
|
|
281
332
|
data-phx-done-refs="{done_refs}"
|
|
282
333
|
data-phx-preflighted-refs="{preflighted_refs}"
|
|
283
334
|
data-phx-update="ignore" phx-hook="Phoenix.LiveFileUpload"
|
|
284
|
-
{accept} {multiple}>
|
|
335
|
+
{accept} {multiple} {auto_upload}>
|
|
285
336
|
</input>
|
|
286
337
|
"""
|
|
287
338
|
)
|
pyview/ws_handler.py
CHANGED
|
@@ -340,7 +340,11 @@ class LiveSocketHandler:
|
|
|
340
340
|
)
|
|
341
341
|
|
|
342
342
|
if event == "progress":
|
|
343
|
+
# Trigger progress callback BEFORE updating progress (which may consume the entry)
|
|
344
|
+
await socket.upload_manager.trigger_progress_callback_if_exists(payload, socket)
|
|
345
|
+
|
|
343
346
|
socket.upload_manager.update_progress(joinRef, payload)
|
|
347
|
+
|
|
344
348
|
rendered = await _render(socket)
|
|
345
349
|
diff = socket.diff(rendered)
|
|
346
350
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyview-web
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: LiveView in Python
|
|
5
5
|
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
6
7
|
Keywords: web,api,LiveView
|
|
7
8
|
Author: Larry Ogrodnek
|
|
8
9
|
Author-email: ogrodnek@gmail.com
|
|
9
|
-
Requires-Python: >=3.11,<3.
|
|
10
|
+
Requires-Python: >=3.11,<3.14
|
|
10
11
|
Classifier: Development Status :: 4 - Beta
|
|
11
12
|
Classifier: Environment :: Web Environment
|
|
12
13
|
Classifier: Framework :: AsyncIO
|
|
@@ -20,6 +21,7 @@ Classifier: Programming Language :: Python
|
|
|
20
21
|
Classifier: Programming Language :: Python :: 3
|
|
21
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
25
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
24
26
|
Classifier: Topic :: Internet
|
|
25
27
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
@@ -34,7 +36,7 @@ Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
|
34
36
|
Requires-Dist: itsdangerous (>=2.2.0,<3.0.0)
|
|
35
37
|
Requires-Dist: markupsafe (>=3.0.2,<4.0.0)
|
|
36
38
|
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
|
|
37
|
-
Requires-Dist: starlette (
|
|
39
|
+
Requires-Dist: starlette (>=0.47.2,<0.48.0)
|
|
38
40
|
Requires-Dist: uvicorn (==0.34.3)
|
|
39
41
|
Requires-Dist: wsproto (==1.2.0)
|
|
40
42
|
Project-URL: Homepage, https://pyview.rocks
|
|
@@ -21,7 +21,7 @@ pyview/instrumentation/interfaces.py,sha256=AhVDM_vzETWtM-wfOXaM13K2OgdL0H8lu5wh
|
|
|
21
21
|
pyview/instrumentation/noop.py,sha256=VP8UjiI--A7KWqnSFh7PMG7MqY0Z9ddQjBYVW7iHZa0,2941
|
|
22
22
|
pyview/js.py,sha256=E6HMsUfXQjrcLqYq26ieeYuzTjBeZqfJwwOm3uSR4ME,3498
|
|
23
23
|
pyview/live_routes.py,sha256=IN2Jmy8b1umcfx1R7ZgFXHZNbYDJp_kLIbADtDJknPM,1749
|
|
24
|
-
pyview/live_socket.py,sha256=
|
|
24
|
+
pyview/live_socket.py,sha256=s41hancNG_hJOl17SSSc2Bg2UzxbBF8mihk4AedFy3k,6972
|
|
25
25
|
pyview/live_view.py,sha256=mwAp7jiABSZCBgYF-GLQCB7zcJ7Wpz9cuC84zjzsp2U,1455
|
|
26
26
|
pyview/meta.py,sha256=01Z-qldB9jrewmIJHQpUqyIhuHodQGgCvpuY9YM5R6c,74
|
|
27
27
|
pyview/phx_message.py,sha256=DUdPfl6tlw9K0FNXJ35ehq03JGgynvwA_JItHQ_dxMQ,2007
|
|
@@ -36,7 +36,7 @@ pyview/template/render_diff.py,sha256=1P-OgtcGb0Y-zJ9uUH3bKWX-qQTHBa4jgg73qJD7eg
|
|
|
36
36
|
pyview/template/root_template.py,sha256=zCUs1bt8R7qynhBE0tTSEYfdkGtbeKNmPhwzRiFNdsI,2031
|
|
37
37
|
pyview/template/serializer.py,sha256=WDZfqJr2LMlf36fUW2CmWc2aREc63553_y_GRP2-qYc,826
|
|
38
38
|
pyview/template/utils.py,sha256=S8593UjUJztUrtC3h1EL9MxQp5uH7rFDTNkv9C6A_xU,642
|
|
39
|
-
pyview/uploads.py,sha256=
|
|
39
|
+
pyview/uploads.py,sha256=G8zcVoTfWUjFs4nW3XZj60PqWujWTw2Rs3WOyyo52nY,12077
|
|
40
40
|
pyview/vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
41
|
pyview/vendor/flet/pubsub/__init__.py,sha256=JSPCeKB26b5E-IVHNRvVHrlf_CBGDLCulE9ADrostGs,39
|
|
42
42
|
pyview/vendor/flet/pubsub/pub_sub.py,sha256=gpdruSxKQBqL7_Dtxo4vETm1kM0YH7S299msw2oyUoE,10184
|
|
@@ -50,9 +50,9 @@ pyview/vendor/ibis/nodes.py,sha256=TgFt4q5MrVW3gC3PVitrs2LyXKllRveooM7XKydNATk,2
|
|
|
50
50
|
pyview/vendor/ibis/template.py,sha256=6XJXnztw87CrOaKeW3e18LL0fNM8AI6AaK_QgMdb7ew,2353
|
|
51
51
|
pyview/vendor/ibis/tree.py,sha256=hg8f-fKHeo6DE8R-QgAhdvEaZ8rKyz7p0nGwPy0CBTs,2509
|
|
52
52
|
pyview/vendor/ibis/utils.py,sha256=nLSaxPR9vMphzV9qinlz_Iurv9c49Ps6Knv8vyNlewU,2768
|
|
53
|
-
pyview/ws_handler.py,sha256=
|
|
54
|
-
pyview_web-0.
|
|
55
|
-
pyview_web-0.
|
|
56
|
-
pyview_web-0.
|
|
57
|
-
pyview_web-0.
|
|
58
|
-
pyview_web-0.
|
|
53
|
+
pyview/ws_handler.py,sha256=f6iI4vvLesepI9tCTHLyRBbdKCGPS6Gzj_UUjfIrPT8,14722
|
|
54
|
+
pyview_web-0.4.2.dist-info/METADATA,sha256=-tCEwOMH_NssPzXFOfuhSwVQ75TpDGiV6ltYZ3HXsSE,5280
|
|
55
|
+
pyview_web-0.4.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
56
|
+
pyview_web-0.4.2.dist-info/entry_points.txt,sha256=GAT-ic-VYmmSMUSUVKdV1bp4w-vgEeVP-XzElvarQ9U,42
|
|
57
|
+
pyview_web-0.4.2.dist-info/licenses/LICENSE,sha256=M_bADaBm9_MV9llX3lCicksLhwk3eZUjA2srE0uUWr0,1071
|
|
58
|
+
pyview_web-0.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|