pyweber 1.2.0.dev20260331__tar.gz → 1.2.0.dev20260404__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.
- {pyweber-1.2.0.dev20260331/pyweber.egg-info → pyweber-1.2.0.dev20260404}/PKG-INFO +1 -1
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyproject.toml +1 -1
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/connection/websocket.py +37 -5
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/core/element.py +3 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/element.py +36 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/field.py +6 -5
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/field_storage.py +4 -1
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/file.py +2 -1
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/routes.py +36 -7
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/ws_message.py +6 -1
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/pyweber/pyweber.py +12 -9
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/js.js +87 -22
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404/pyweber.egg-info}/PKG-INFO +1 -1
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/LICENSE +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/MANIFEST.in +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/README.md +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/__init__.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/admin/index.html +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/admin/src/script.js +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/admin/src/style.css +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/cli/__init__.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/cli/commands.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/components/__init__.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/components/form.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/components/general.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/components/input.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/config/__init__.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/config/config.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/connection/__init__.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/connection/http.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/connection/reload.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/connection/selector.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/connection/session.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/core/__init__.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/core/events.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/core/template.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/core/window.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/__init__.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/cookies.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/create_app.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/error_pages.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/headers.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/middleware.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/openapi.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/request.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/response.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/run.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/task_manager.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/models/template_diff.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/pyweber/__init__.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/.gitignore +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/config.toml +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/css.css +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/docs.html +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/favicon/android-chrome-192x192.png +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/favicon/android-chrome-512x512.png +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/favicon/apple-touch-icon.png +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/favicon/favicon-16x16.png +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/favicon/favicon-32x32.png +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/favicon/favicon.ico +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/favicon/pyweber.png +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/favicon/site.webmanifest +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/handlers.js +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/html.html +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/html401.html +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/html404.html +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/html500.html +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/loading.html +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/main.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/pyweber.css +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/update.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/utils/__init__.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/utils/exceptions.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/utils/loads.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/utils/types.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/utils/utils.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber.egg-info/SOURCES.txt +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber.egg-info/dependency_links.txt +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber.egg-info/entry_points.txt +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber.egg-info/requires.txt +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber.egg-info/top_level.txt +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/setup.cfg +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/tests/test_config.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/tests/test_cookies.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/tests/test_response.py +0 -0
- {pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/tests/test_template_diff.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyweber"
|
|
7
|
-
version = "1.2.0.
|
|
7
|
+
version = "1.2.0.dev20260404"
|
|
8
8
|
description = "A lightweight Python framework for building and managing web applications."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -34,7 +34,8 @@ def need_message_keys(): # pragma: no cover
|
|
|
34
34
|
'window_data',
|
|
35
35
|
'window_response',
|
|
36
36
|
'window_event',
|
|
37
|
-
'sessionId'
|
|
37
|
+
'sessionId',
|
|
38
|
+
'file_content'
|
|
38
39
|
]
|
|
39
40
|
|
|
40
41
|
def event_is_running(message: wsMessage, task_manager: TaskManager) -> bool: # pragma: no cover
|
|
@@ -264,6 +265,7 @@ class BaseWebsockets: # pragma: no cover
|
|
|
264
265
|
self.task_manager = TaskManager()
|
|
265
266
|
self.ws_connections: dict[str, Union[WebsocketServer, Callable[..., Any]]] = {}
|
|
266
267
|
self._window_response_future: dict[str, asyncio.Future] = {}
|
|
268
|
+
self._file_content_future: dict[str, asyncio.Future] = {}
|
|
267
269
|
self.old_template: 'Template' = None
|
|
268
270
|
self.app = app
|
|
269
271
|
|
|
@@ -275,9 +277,8 @@ class BaseWebsockets: # pragma: no cover
|
|
|
275
277
|
assert isinstance(value, dict)
|
|
276
278
|
self.__window_response = value
|
|
277
279
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
event_handler = EventConstrutor(
|
|
280
|
+
def event_handler(self, message: wsMessage):
|
|
281
|
+
return EventConstrutor(
|
|
281
282
|
target_id=message.target_uuid,
|
|
282
283
|
current_target_id=message.current_target_uuid,
|
|
283
284
|
app=self.app,
|
|
@@ -287,6 +288,10 @@ class BaseWebsockets: # pragma: no cover
|
|
|
287
288
|
event_data=message.event_data,
|
|
288
289
|
event_type=message.type
|
|
289
290
|
).build_event()
|
|
291
|
+
|
|
292
|
+
async def message_handler(self, message: wsMessage):
|
|
293
|
+
|
|
294
|
+
event_handler = self.event_handler(message)
|
|
290
295
|
|
|
291
296
|
if message.event_ref == 'document':
|
|
292
297
|
if event_handler.current_target:
|
|
@@ -414,6 +419,25 @@ class BaseWebsockets: # pragma: no cover
|
|
|
414
419
|
if future and not future.done():
|
|
415
420
|
future.set_result(response)
|
|
416
421
|
|
|
422
|
+
async def get_file_content(self, timeout: int, file_id: str):
|
|
423
|
+
if file_id not in self._file_content_future:
|
|
424
|
+
self._file_content_future[file_id] = asyncio.get_event_loop().create_future()
|
|
425
|
+
try:
|
|
426
|
+
result = await asyncio.wait_for(self._file_content_future[file_id], timeout)
|
|
427
|
+
return result
|
|
428
|
+
except asyncio.TimeoutError:
|
|
429
|
+
return None
|
|
430
|
+
finally:
|
|
431
|
+
self._file_content_future.pop(file_id, None)
|
|
432
|
+
|
|
433
|
+
async def set_file_content(self, response: dict, file_id: str):
|
|
434
|
+
self.file_content = response
|
|
435
|
+
|
|
436
|
+
future = self._file_content_future.get(file_id)
|
|
437
|
+
|
|
438
|
+
if future and not future.done():
|
|
439
|
+
future.set_result(response)
|
|
440
|
+
|
|
417
441
|
async def get_template_diff(self, session: Session):
|
|
418
442
|
|
|
419
443
|
diff = TemplateDiff()
|
|
@@ -516,7 +540,9 @@ class WebsocketManager(BaseWebsockets): # pragma: no cover
|
|
|
516
540
|
if not message.get('template') and message.get('window_data'):
|
|
517
541
|
return {}
|
|
518
542
|
|
|
519
|
-
|
|
543
|
+
route = message.get('route', '')
|
|
544
|
+
route = route[:-1] if route.endswith('/') and len(route) > 1 else route
|
|
545
|
+
if route not in self.app.list_routes:
|
|
520
546
|
return {}
|
|
521
547
|
|
|
522
548
|
return message
|
|
@@ -533,6 +559,9 @@ class WebsocketManager(BaseWebsockets): # pragma: no cover
|
|
|
533
559
|
|
|
534
560
|
message = wsMessage(raw_message=raw_message, app=self.app, ws=self)
|
|
535
561
|
|
|
562
|
+
if message.file_content:
|
|
563
|
+
await self.set_file_content(message.file_content, message.file_content.get('file_id'))
|
|
564
|
+
|
|
536
565
|
if message.window_response:
|
|
537
566
|
await self.set_window_response(message.window_response, message.session_id)
|
|
538
567
|
|
|
@@ -591,6 +620,9 @@ class WebsocketManager(BaseWebsockets): # pragma: no cover
|
|
|
591
620
|
|
|
592
621
|
if message.window_response:
|
|
593
622
|
await self.set_window_response(message.window_response, message.session_id)
|
|
623
|
+
|
|
624
|
+
if message.file_content:
|
|
625
|
+
await self.set_file_content(message.file_content, message.session_id)
|
|
594
626
|
|
|
595
627
|
sync_template = await self.get_sync_template(message=message)
|
|
596
628
|
|
|
@@ -278,6 +278,9 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
278
278
|
raise TypeError(f'all childs must be str or Element instances, but got {type(child).__name__}')
|
|
279
279
|
|
|
280
280
|
return new_childs
|
|
281
|
+
|
|
282
|
+
def update(self):
|
|
283
|
+
raise NotImplementedError
|
|
281
284
|
|
|
282
285
|
def __deepy_clone(self, obj):
|
|
283
286
|
if isinstance(obj, list):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from uuid import uuid4
|
|
2
2
|
import re
|
|
3
|
+
import inspect
|
|
3
4
|
|
|
4
5
|
from typing import (
|
|
5
6
|
TYPE_CHECKING,
|
|
@@ -20,6 +21,7 @@ from pyweber.models.file import File
|
|
|
20
21
|
if TYPE_CHECKING: # pragma: no cover
|
|
21
22
|
from pyweber.core.template import Template
|
|
22
23
|
from pyweber.core.element import Element
|
|
24
|
+
from pyweber.connection.websocket import WebsocketManager
|
|
23
25
|
|
|
24
26
|
class ChildElements(list['Element']): # pragma: no cover
|
|
25
27
|
def __init__(self, parent: 'Element'):
|
|
@@ -117,6 +119,40 @@ class ElementConstrutor: # pragma: no cover
|
|
|
117
119
|
def template(self, value: 'Template'):
|
|
118
120
|
self.__template = value
|
|
119
121
|
|
|
122
|
+
async def stream(
|
|
123
|
+
self,
|
|
124
|
+
file: File,
|
|
125
|
+
ws_server: WebsocketManager,
|
|
126
|
+
session_id: str,
|
|
127
|
+
speed_max: int = 8192,
|
|
128
|
+
timeout: float = 5,
|
|
129
|
+
updater_event: Callable[..., Any] = None
|
|
130
|
+
):
|
|
131
|
+
parts = file.size // speed_max + 1
|
|
132
|
+
content = b''
|
|
133
|
+
|
|
134
|
+
for part in range(parts):
|
|
135
|
+
await ws_server.send_message(
|
|
136
|
+
data={'read_file': file.file_id, 'start': speed_max * part, 'end': speed_max * (part+1)},
|
|
137
|
+
session_id=session_id
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
response = await ws_server.get_file_content(timeout=timeout, file_id=file.file_id)
|
|
141
|
+
|
|
142
|
+
if isinstance(updater_event, Callable):
|
|
143
|
+
if inspect.iscoroutinefunction(updater_event):
|
|
144
|
+
await updater_event(**response)
|
|
145
|
+
else:
|
|
146
|
+
updater_event(**response)
|
|
147
|
+
|
|
148
|
+
if response.get('status') == 'sucess':
|
|
149
|
+
content += bytes(response.get('data').values())
|
|
150
|
+
|
|
151
|
+
if file.size == len(content):
|
|
152
|
+
return content
|
|
153
|
+
|
|
154
|
+
raise ValueError('File content incomplete.')
|
|
155
|
+
|
|
120
156
|
@property
|
|
121
157
|
def files(self):
|
|
122
158
|
if self.tag == 'input' and self.attrs.get('type', None) == 'file':
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
4
|
|
|
8
5
|
class Field: # pragma: no cover
|
|
9
6
|
def __init__(
|
|
@@ -11,15 +8,19 @@ class Field: # pragma: no cover
|
|
|
11
8
|
name: Optional[str] = None,
|
|
12
9
|
filename: Optional[str] = None,
|
|
13
10
|
value: Optional[bytes] = b'',
|
|
14
|
-
content_type: Optional[str] = None
|
|
11
|
+
content_type: Optional[str] = None,
|
|
12
|
+
field_id: Optional[str] = None,
|
|
13
|
+
size: Optional[int] = 0
|
|
15
14
|
):
|
|
16
15
|
self.name = name
|
|
17
16
|
self.filename = filename
|
|
18
17
|
self.__value = value
|
|
19
18
|
self.content_type = content_type
|
|
19
|
+
self.field_id = field_id
|
|
20
|
+
self.size = size
|
|
20
21
|
|
|
21
22
|
@property
|
|
22
23
|
def value(self): return bytes(self.__value)
|
|
23
24
|
|
|
24
25
|
def __repr__(self):
|
|
25
|
-
return f"Field(name={self.name}, value_length={
|
|
26
|
+
return f"Field(name={self.name}, value_length={self.size})"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import re
|
|
2
|
+
from uuid import uuid4
|
|
2
3
|
from pyweber.models.field import Field
|
|
3
4
|
|
|
4
5
|
class FieldStorage: # pragma: no cover
|
|
@@ -51,13 +52,14 @@ class FieldStorage: # pragma: no cover
|
|
|
51
52
|
name = re.search(r"name=\"(.+)\"", keys)
|
|
52
53
|
filename = re.search(r"filename=\"(.+)\"", keys)
|
|
53
54
|
content_type = re.search(r"Content-Type: (.+)", keys)
|
|
54
|
-
field = Field()
|
|
55
|
+
field = Field(field_id=str(uuid4()))
|
|
55
56
|
|
|
56
57
|
if filename:
|
|
57
58
|
field.content_type = content_type.group(1)
|
|
58
59
|
field.name = name.group(1).split('";')[0]
|
|
59
60
|
field.filename = filename.group(1)
|
|
60
61
|
field.value = v
|
|
62
|
+
field.size = len(field.value)
|
|
61
63
|
|
|
62
64
|
fids.append(field)
|
|
63
65
|
elif name:
|
|
@@ -65,6 +67,7 @@ class FieldStorage: # pragma: no cover
|
|
|
65
67
|
field.content_type = None
|
|
66
68
|
field.value = v.decode()
|
|
67
69
|
field.filename = None
|
|
70
|
+
field.size = len(field.value)
|
|
68
71
|
fids.append(field)
|
|
69
72
|
|
|
70
73
|
return fids
|
|
@@ -4,8 +4,9 @@ class File: # pragma: no cover
|
|
|
4
4
|
def __init__(self, field: Field):
|
|
5
5
|
self.filename = field.filename
|
|
6
6
|
self.content = field.value
|
|
7
|
-
self.size =
|
|
7
|
+
self.size = field.size
|
|
8
8
|
self.content_type = field.content_type
|
|
9
|
+
self.file_id = field.field_id
|
|
9
10
|
|
|
10
11
|
def __len__(self):
|
|
11
12
|
return self.size
|
|
@@ -77,10 +77,12 @@ class Route: # pragma: no cover
|
|
|
77
77
|
title: str = '',
|
|
78
78
|
process_response: bool = True,
|
|
79
79
|
callback: Callable[..., Any] = None,
|
|
80
|
+
query_paramns: list[str] = None,
|
|
80
81
|
**kwargs
|
|
81
82
|
):
|
|
82
83
|
self.group = group
|
|
83
84
|
self.route = route
|
|
85
|
+
self.query_params = query_paramns
|
|
84
86
|
self.template = template
|
|
85
87
|
self.methods = methods or self.default_method()
|
|
86
88
|
self.name = name
|
|
@@ -92,6 +94,17 @@ class Route: # pragma: no cover
|
|
|
92
94
|
self.callback = callback
|
|
93
95
|
self.kwargs = kwargs
|
|
94
96
|
|
|
97
|
+
@property
|
|
98
|
+
def query_params(self):
|
|
99
|
+
return self.__query_params
|
|
100
|
+
|
|
101
|
+
@query_params.setter
|
|
102
|
+
def query_params(self, value: list[str]):
|
|
103
|
+
if value and not isinstance(value, list):
|
|
104
|
+
raise ValueError("Query Paraments isn't not valid")
|
|
105
|
+
|
|
106
|
+
self.__query_params = value
|
|
107
|
+
|
|
95
108
|
@property
|
|
96
109
|
def callback(self): return self.__callback
|
|
97
110
|
|
|
@@ -297,12 +310,20 @@ class RouteManager: # pragma: no cover
|
|
|
297
310
|
self.__route_names: dict[str, str] = {}
|
|
298
311
|
self.groups: list[str] = []
|
|
299
312
|
|
|
313
|
+
|
|
314
|
+
def __partition__(self, route: str, spliter: str):
|
|
315
|
+
p, _, q = route.partition(spliter)
|
|
316
|
+
return p, q
|
|
317
|
+
|
|
318
|
+
def __get_query_params__(self, params_str: str):
|
|
319
|
+
return [val.split('=',1)[0] for val in params_str.split('&')] if params_str else []
|
|
320
|
+
|
|
300
321
|
def is_redirected(self, route: str) -> bool:
|
|
301
322
|
return self.get_redirected_route(route=route) is not None
|
|
302
323
|
|
|
303
324
|
@property
|
|
304
325
|
def default_group(self): return Route.default_group()
|
|
305
|
-
|
|
326
|
+
|
|
306
327
|
@property
|
|
307
328
|
def list_routes(self) -> list[str]:
|
|
308
329
|
return [route.full_route for route in self.__routes.values() if '_pyweber' not in str(route.route)]
|
|
@@ -326,8 +347,17 @@ class RouteManager: # pragma: no cover
|
|
|
326
347
|
def to_route(self, target: str, status_code: int = 302, **kwargs):
|
|
327
348
|
if not self.is_redirect_status_code(status_code=status_code):
|
|
328
349
|
raise ValueError(f'status code {status_code} is invalid Redirect HttpStatusCode')
|
|
350
|
+
|
|
351
|
+
route = self.get_route_by_name(target)
|
|
352
|
+
|
|
353
|
+
if not route:
|
|
354
|
+
route = self.get_route_by_path(target)
|
|
355
|
+
|
|
356
|
+
if route:
|
|
357
|
+
_, kwd = self.resolve_path(target)
|
|
358
|
+
|
|
359
|
+
kwargs = {**kwargs, **kwd}
|
|
329
360
|
|
|
330
|
-
route = self.get_route_by_name(name=target) or self.get_route_by_path(route=target)
|
|
331
361
|
if route:
|
|
332
362
|
return RedirectRoute(route=route, status_code=status_code, **kwargs)
|
|
333
363
|
|
|
@@ -370,10 +400,6 @@ class RouteManager: # pragma: no cover
|
|
|
370
400
|
|
|
371
401
|
else:
|
|
372
402
|
response = handler(**kwargs)
|
|
373
|
-
|
|
374
|
-
# self.__routes.get(route).template = response
|
|
375
|
-
|
|
376
|
-
# return self.__routes.get(route).template
|
|
377
403
|
return response
|
|
378
404
|
|
|
379
405
|
self.add_route(
|
|
@@ -406,6 +432,8 @@ class RouteManager: # pragma: no cover
|
|
|
406
432
|
process_response: bool = True,
|
|
407
433
|
**kwargs
|
|
408
434
|
):
|
|
435
|
+
route, query_params = self.__partition__(route, '/?')
|
|
436
|
+
|
|
409
437
|
group = self.get_group(group=group)
|
|
410
438
|
if self.full_route(route=route, group=group) in self.__routes:
|
|
411
439
|
raise RouteAlreadyExistError(route=route)
|
|
@@ -429,7 +457,8 @@ class RouteManager: # pragma: no cover
|
|
|
429
457
|
content_type=content_type,
|
|
430
458
|
title=title,
|
|
431
459
|
process_response=process_response,
|
|
432
|
-
callback=handler
|
|
460
|
+
callback=handler,
|
|
461
|
+
query_paramns=self.__get_query_params__(query_params)
|
|
433
462
|
)
|
|
434
463
|
|
|
435
464
|
self.__routes[_route.full_route] = _route
|
|
@@ -30,6 +30,10 @@ class wsMessage: # pragma: no cover
|
|
|
30
30
|
@property
|
|
31
31
|
def window_response(self) -> dict[str, (str, int)]:
|
|
32
32
|
return self.__raw_message.get('window_response', {})
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def file_content(self) -> dict[str, Any]:
|
|
36
|
+
return self.__raw_message.get('file_content', {})
|
|
33
37
|
|
|
34
38
|
def get_value(self, key: str):
|
|
35
39
|
return self.__raw_message.get(key, None)
|
|
@@ -113,7 +117,8 @@ class wsMessage: # pragma: no cover
|
|
|
113
117
|
name=None,
|
|
114
118
|
filename=value.get('name'),
|
|
115
119
|
content_type=value.get('content_type'),
|
|
116
|
-
|
|
120
|
+
field_id=value.get('file_id'),
|
|
121
|
+
size=int(value.get('size'))
|
|
117
122
|
)
|
|
118
123
|
) for value in values.get('value', []) if isinstance(value, dict)
|
|
119
124
|
])
|
|
@@ -163,14 +163,15 @@ class Pyweber(
|
|
|
163
163
|
content_type=ContentTypes.html,
|
|
164
164
|
redirect_path=request.path,
|
|
165
165
|
callback=None,
|
|
166
|
-
kwargs=
|
|
166
|
+
kwargs=request.query_params
|
|
167
167
|
)
|
|
168
168
|
)
|
|
169
169
|
|
|
170
170
|
else:
|
|
171
171
|
template_result = await self.get_template(
|
|
172
172
|
route=request.path,
|
|
173
|
-
method=request.method
|
|
173
|
+
method=request.method,
|
|
174
|
+
**request.query_params
|
|
174
175
|
)
|
|
175
176
|
|
|
176
177
|
content_result = self.template_to_bytes(
|
|
@@ -286,8 +287,10 @@ class Pyweber(
|
|
|
286
287
|
return ContentTypes.unkown
|
|
287
288
|
return ContentTypes.html
|
|
288
289
|
|
|
289
|
-
async def get_template(self, route: str, method: str = 'GET'):
|
|
290
|
-
path,
|
|
290
|
+
async def get_template(self, route: str, method: str = 'GET', **kwargs):
|
|
291
|
+
path, kwd = self.resolve_path(route=route)
|
|
292
|
+
|
|
293
|
+
kwargs = {**kwargs, **kwd}
|
|
291
294
|
|
|
292
295
|
state_result = StateResult(
|
|
293
296
|
template=None,
|
|
@@ -417,17 +420,17 @@ class Pyweber(
|
|
|
417
420
|
|
|
418
421
|
while callable(template) or isinstance(template, RedirectRoute):
|
|
419
422
|
if callable(template):
|
|
420
|
-
|
|
423
|
+
request_params = {**self.request.body, **self.request.query_params} if self.request else {}
|
|
421
424
|
kwargs = OpenApiProcessor.prepare_callback_kwargs(
|
|
422
425
|
callback=state_result.callback,
|
|
423
|
-
**{**state_result.kwargs, **
|
|
426
|
+
**{**state_result.kwargs, **request_params}
|
|
424
427
|
)
|
|
425
428
|
|
|
426
429
|
template = await template(**kwargs) if inspect.iscoroutinefunction(template) else template(**kwargs)
|
|
427
|
-
|
|
428
|
-
if isinstance(template, RedirectRoute):
|
|
429
|
-
redirect_path = self.build_route(route=template.route.full_route, **state_result.kwargs)
|
|
430
430
|
|
|
431
|
+
if isinstance(template, RedirectRoute):
|
|
432
|
+
redirect_path = self.build_route(route=template.route.full_route, **{**state_result.kwargs, **template.kwargs})
|
|
433
|
+
|
|
431
434
|
self._check_recursion(route=redirect_path)
|
|
432
435
|
state_result = await self._process_redirect_route(
|
|
433
436
|
state=state_result,
|
|
@@ -3,6 +3,8 @@ let socketReady = false;
|
|
|
3
3
|
let window_event_received = false;
|
|
4
4
|
const earyEventsBuffer = [];
|
|
5
5
|
const activeListeners = [];
|
|
6
|
+
const fileMap = {};
|
|
7
|
+
const fileSignatureMap = {};
|
|
6
8
|
|
|
7
9
|
const EventRef = new Proxy(
|
|
8
10
|
{DOCUMENT: 'document', WINDOW: 'window'},
|
|
@@ -62,8 +64,18 @@ function connectWebSocket() {
|
|
|
62
64
|
}
|
|
63
65
|
};
|
|
64
66
|
|
|
65
|
-
if (data.
|
|
66
|
-
|
|
67
|
+
if (data.read_file) {
|
|
68
|
+
const response = await get_file_content(
|
|
69
|
+
data.read_file,
|
|
70
|
+
data.start,
|
|
71
|
+
data.end
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const event_data = await getEventData(
|
|
75
|
+
{file_content: response}
|
|
76
|
+
);
|
|
77
|
+
socket.send(JSON.stringify(event_data));
|
|
78
|
+
}
|
|
67
79
|
|
|
68
80
|
if (data.template) {
|
|
69
81
|
applyDifferences(data.template);
|
|
@@ -377,7 +389,7 @@ function fromBase64(base64) {
|
|
|
377
389
|
return new TextDecoder().decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0)));
|
|
378
390
|
};
|
|
379
391
|
|
|
380
|
-
async function getEventData({type=null, event=null, event_ref=null, window_response=null}) {
|
|
392
|
+
async function getEventData({type=null, event=null, event_ref=null, window_response=null, file_content=null}) {
|
|
381
393
|
let target;
|
|
382
394
|
|
|
383
395
|
if (event){
|
|
@@ -446,6 +458,7 @@ async function getEventData({type=null, event=null, event_ref=null, window_respo
|
|
|
446
458
|
window_data: JSON.stringify(getWindowData()),
|
|
447
459
|
window_response: window_response ? window_response : {},
|
|
448
460
|
window_event: event_ref === EventRef.WINDOW ? sessionStorage.getItem(type): null,
|
|
461
|
+
file_content: file_content ? file_content : {},
|
|
449
462
|
sessionId: getsessionId()
|
|
450
463
|
};
|
|
451
464
|
|
|
@@ -496,10 +509,70 @@ function getWindowData() {
|
|
|
496
509
|
return windowData;
|
|
497
510
|
}
|
|
498
511
|
|
|
512
|
+
async function get_file_content(file_id, start, end) {
|
|
513
|
+
const file = fileMap[file_id];
|
|
514
|
+
|
|
515
|
+
if (!file) {
|
|
516
|
+
return {
|
|
517
|
+
data: 'File not Found',
|
|
518
|
+
file_id: file_id,
|
|
519
|
+
status: 'error'
|
|
520
|
+
};
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
const fileSize = file.size;
|
|
524
|
+
const safeStart = Math.max(0, start);
|
|
525
|
+
const safeEnd = end !== null ? Math.min(end, fileSize) : fileSize;
|
|
526
|
+
|
|
527
|
+
if (safeStart >= fileSize) {
|
|
528
|
+
return {
|
|
529
|
+
data: 'Start index does not gather than filesize',
|
|
530
|
+
start: safeStart,
|
|
531
|
+
end: safeEnd,
|
|
532
|
+
size: fileSize,
|
|
533
|
+
file_id: file_id,
|
|
534
|
+
status: 'error'
|
|
535
|
+
};
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
const chunk = file.slice(safeStart, safeEnd);
|
|
539
|
+
const buffer = await chunk.arrayBuffer();
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
data: new Uint8Array(buffer),
|
|
543
|
+
start: safeStart,
|
|
544
|
+
end: safeEnd,
|
|
545
|
+
size: fileSize,
|
|
546
|
+
file_id: file_id,
|
|
547
|
+
status: 'sucess'
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function getOrCreateFileId(file) {
|
|
552
|
+
// 1. tenta por referência
|
|
553
|
+
for (const [id, existingFile] of Object.entries(fileMap)) {
|
|
554
|
+
if (existingFile === file) return id;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// 2. tenta por assinatura
|
|
558
|
+
const signature = `${file.name}_${file.size}_${file.lastModified}`;
|
|
559
|
+
|
|
560
|
+
if (fileSignatureMap[signature]) {
|
|
561
|
+
return fileSignatureMap[signature];
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// 3. cria novo
|
|
565
|
+
const file_id = crypto.randomUUID();
|
|
566
|
+
|
|
567
|
+
fileMap[file_id] = file;
|
|
568
|
+
fileSignatureMap[signature] = file_id;
|
|
569
|
+
|
|
570
|
+
return file_id;
|
|
571
|
+
}
|
|
572
|
+
|
|
499
573
|
async function getFormValues() {
|
|
500
574
|
const values = {};
|
|
501
575
|
const inputs = document.querySelectorAll('input, textarea, select, option');
|
|
502
|
-
|
|
503
576
|
|
|
504
577
|
for (const input of inputs) {
|
|
505
578
|
const id = input.getAttribute('uuid');
|
|
@@ -525,24 +598,16 @@ async function getFormValues() {
|
|
|
525
598
|
if (input.files) {
|
|
526
599
|
const files = Array.from(input.files);
|
|
527
600
|
|
|
528
|
-
const arquivos =
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
size: file.size,
|
|
539
|
-
name: file.name,
|
|
540
|
-
content_type: file.type || "application/octet-stream",
|
|
541
|
-
});
|
|
542
|
-
};
|
|
543
|
-
reader.readAsArrayBuffer(file);
|
|
544
|
-
})
|
|
545
|
-
}));
|
|
601
|
+
const arquivos = files.map(file => {
|
|
602
|
+
const file_id = getOrCreateFileId(file);
|
|
603
|
+
|
|
604
|
+
return {
|
|
605
|
+
size: file.size,
|
|
606
|
+
name: file.name,
|
|
607
|
+
file_id: file_id,
|
|
608
|
+
content_type: file.type || "application/octet-stream",
|
|
609
|
+
}
|
|
610
|
+
});
|
|
546
611
|
|
|
547
612
|
values[id]['value'] = arquivos;
|
|
548
613
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/favicon/apple-touch-icon.png
RENAMED
|
File without changes
|
{pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/favicon/favicon-16x16.png
RENAMED
|
File without changes
|
{pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/favicon/favicon-32x32.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber/static/favicon/site.webmanifest
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyweber-1.2.0.dev20260331 → pyweber-1.2.0.dev20260404}/pyweber.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|