pyweber 1.2.0.dev20260424__tar.gz → 1.2.0.dev20260425__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.dev20260424/pyweber.egg-info → pyweber-1.2.0.dev20260425}/PKG-INFO +1 -1
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyproject.toml +1 -1
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/core/element.py +130 -49
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/core/template.py +5 -92
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/element.py +20 -15
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425/pyweber.egg-info}/PKG-INFO +1 -1
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/LICENSE +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/MANIFEST.in +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/README.md +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/__init__.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/admin/index.html +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/admin/src/script.js +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/admin/src/style.css +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/cli/__init__.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/cli/commands.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/components/__init__.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/components/form.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/components/general.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/components/input.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/config/__init__.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/config/config.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/connection/__init__.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/connection/http.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/connection/reload.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/connection/selector.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/connection/session.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/connection/websocket.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/core/__init__.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/core/events.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/core/window.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/__init__.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/cookies.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/create_app.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/error_pages.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/field.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/field_storage.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/file.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/file_stream.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/headers.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/middleware.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/openapi.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/request.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/response.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/routes.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/run.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/strem_stats.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/task_manager.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/template_diff.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/models/ws_message.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/pyweber/__init__.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/pyweber/pyweber.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/.gitignore +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/config.toml +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/css.css +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/docs.html +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/android-chrome-192x192.png +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/android-chrome-512x512.png +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/apple-touch-icon.png +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/favicon-16x16.png +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/favicon-32x32.png +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/favicon.ico +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/pyweber.png +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/site.webmanifest +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/handlers.js +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/html.html +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/html401.html +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/html404.html +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/html500.html +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/js.js +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/loading.html +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/main.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/pyweber.css +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/update.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/utils/__init__.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/utils/exceptions.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/utils/loads.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/utils/types.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/utils/utils.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/SOURCES.txt +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/dependency_links.txt +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/entry_points.txt +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/requires.txt +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/top_level.txt +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/setup.cfg +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/tests/test_config.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/tests/test_cookies.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/tests/test_response.py +0 -0
- {pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/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.dev20260425"
|
|
8
8
|
description = "A lightweight Python framework for building and managing web applications."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "DevPythonMZ", email = "pypi.dev@gmail.com" }]
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from uuid import uuid4
|
|
2
|
+
import lxml.html as HTMLPARSER
|
|
3
|
+
from lxml.html import fromstring
|
|
2
4
|
from typing import Union, Any, Literal
|
|
3
5
|
from pyweber.utils.types import HTMLTag, GetBy
|
|
4
6
|
from pyweber.models.file import File
|
|
@@ -23,6 +25,7 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
23
25
|
data: Any = None,
|
|
24
26
|
sanitize: bool = False,
|
|
25
27
|
files: list[File] = None,
|
|
28
|
+
include_uuid: bool = True,
|
|
26
29
|
**kwargs: str
|
|
27
30
|
):
|
|
28
31
|
super().__init__(
|
|
@@ -37,72 +40,73 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
37
40
|
events=events,
|
|
38
41
|
sanitize=sanitize,
|
|
39
42
|
files=files,
|
|
43
|
+
include_uuid=include_uuid,
|
|
40
44
|
**kwargs
|
|
41
45
|
)
|
|
42
46
|
self.uuid = getattr(self, 'uuid', None) or str(uuid4())
|
|
43
47
|
self.data = data
|
|
44
48
|
self.__element_methods: dict[str, dict[str, Any]] = {}
|
|
45
|
-
|
|
49
|
+
|
|
46
50
|
@property
|
|
47
51
|
def parent(self):
|
|
48
52
|
return self.__parent
|
|
49
|
-
|
|
53
|
+
|
|
50
54
|
@parent.setter
|
|
51
55
|
def parent(self, value: 'Element'):
|
|
52
56
|
if value is None:
|
|
53
57
|
self.__parent = None
|
|
54
58
|
return
|
|
55
|
-
|
|
59
|
+
|
|
56
60
|
if not isinstance(value, Element):
|
|
57
61
|
raise TypeError("Parent must be an Element instance")
|
|
58
62
|
|
|
59
63
|
self.__parent = value
|
|
60
|
-
|
|
64
|
+
|
|
61
65
|
@property
|
|
62
66
|
def childs(self):
|
|
63
67
|
return self.__childs
|
|
64
|
-
|
|
68
|
+
|
|
65
69
|
@childs.setter
|
|
66
70
|
def childs(self, value: ChildElements):
|
|
67
71
|
if not isinstance(value, (list, ChildElements)):
|
|
68
72
|
raise TypeError(f"Children must be a ChildElements instances, but got {type(value).__name__}")
|
|
69
|
-
|
|
73
|
+
|
|
70
74
|
if isinstance(value, list):
|
|
71
75
|
value = ChildElements(self).extend(value)
|
|
72
76
|
|
|
73
77
|
value = self.__render_dynamic_elements(childs=value)
|
|
74
|
-
|
|
78
|
+
|
|
75
79
|
self.__childs = value
|
|
76
|
-
|
|
80
|
+
|
|
77
81
|
@property
|
|
78
82
|
def index(self) -> Union[int, None]:
|
|
79
83
|
return self.parent.childs.index(self) if self.parent else None
|
|
80
|
-
|
|
84
|
+
|
|
81
85
|
def first_child(self) -> Union['Element', None]:
|
|
82
86
|
return self.childs[0] if self.childs else None
|
|
83
|
-
|
|
87
|
+
|
|
84
88
|
def last_child(self) -> Union['Element', None]:
|
|
85
89
|
return self.childs[-1] if self.childs else None
|
|
86
|
-
|
|
90
|
+
|
|
87
91
|
def previous_child(self) -> Union['Element', None]:
|
|
88
92
|
if self.parent:
|
|
89
93
|
return self.parent.childs[self.index-1] if len(self.parent.childs) > 0 else None
|
|
90
|
-
|
|
94
|
+
|
|
91
95
|
def next_child(self) ->Union['Element', None]:
|
|
92
96
|
if self.parent:
|
|
93
97
|
return self.parent.childs[self.index+1] if len(self.parent.childs) >= self.index+1 else None
|
|
94
|
-
|
|
98
|
+
|
|
95
99
|
def add_child(self, child: 'Element'):
|
|
96
100
|
if not isinstance(child, Element):
|
|
97
101
|
raise TypeError("Child must be Element instances")
|
|
98
|
-
|
|
102
|
+
|
|
99
103
|
self.__childs.append(child)
|
|
100
104
|
child.parent = self
|
|
101
|
-
|
|
105
|
+
|
|
102
106
|
def remove_child(self, child: 'Element'):
|
|
103
107
|
if child not in self.__childs:
|
|
104
108
|
raise IndexError('Child not defined for this parent Element')
|
|
105
|
-
|
|
109
|
+
|
|
106
110
|
self.__childs.remove(child)
|
|
107
111
|
child.parent = None
|
|
108
112
|
|
|
@@ -111,11 +115,11 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
111
115
|
raise TypeError(f'Index must be a integer, but you got {type(index).__name__}')
|
|
112
116
|
|
|
113
117
|
return self.__childs.pop(index)
|
|
114
|
-
|
|
118
|
+
|
|
115
119
|
def remove(self):
|
|
116
120
|
if self.parent:
|
|
117
121
|
self.parent.remove_child(self)
|
|
118
|
-
|
|
122
|
+
|
|
119
123
|
def focus(self):
|
|
120
124
|
self.set_selection_range(self.selection_end, self.selection_end)
|
|
121
125
|
self.__set_element_methods(method='focus')
|
|
@@ -125,36 +129,36 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
125
129
|
|
|
126
130
|
def select(self):
|
|
127
131
|
self.__set_element_methods(method='select')
|
|
128
|
-
|
|
132
|
+
|
|
129
133
|
def click(self):
|
|
130
134
|
self.__set_element_methods(method='click')
|
|
131
|
-
|
|
135
|
+
|
|
132
136
|
def scroll_into_view(
|
|
133
137
|
self,
|
|
134
138
|
behavior: Literal['instant', 'smooth'] = 'instant',
|
|
135
139
|
block: Literal['start', 'center', 'end'] = 'start'
|
|
136
140
|
):
|
|
137
141
|
self.__set_element_methods(method='scrollIntoView', behavior=behavior, block=block)
|
|
138
|
-
|
|
142
|
+
|
|
139
143
|
def set_selection_range(self, start: int, end: str):
|
|
140
144
|
self.__set_element_methods(method='setSelectionRange', start=start, end=end)
|
|
141
|
-
|
|
145
|
+
|
|
142
146
|
def getElement(self, by: GetBy, value: str, element: 'Element' = None) -> 'Element':
|
|
143
147
|
results = self.getElements(by=by, value=value, element=element)
|
|
144
148
|
|
|
145
149
|
return results[0] if results else None
|
|
146
|
-
|
|
150
|
+
|
|
147
151
|
def getElements(self, by: GetBy, value: str, element: 'Element' = None) -> list['Element']:
|
|
148
152
|
element = element or self
|
|
149
153
|
results: list['Element'] = []
|
|
150
154
|
|
|
151
155
|
if isinstance(by, GetBy):
|
|
152
156
|
by: str = by.value
|
|
153
|
-
|
|
157
|
+
|
|
154
158
|
if by == 'classes':
|
|
155
159
|
if set(value.split()) <= set(element.classes):
|
|
156
160
|
results.append(element)
|
|
157
|
-
|
|
161
|
+
|
|
158
162
|
elif by in ['attrs', 'style']:
|
|
159
163
|
conditions: list[str] = [pair.strip() for pair in value.split(';') if pair.strip()]
|
|
160
164
|
has = False
|
|
@@ -171,24 +175,24 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
171
175
|
|
|
172
176
|
elif key.strip() in el and el.get(key.strip(), None) == val.strip():
|
|
173
177
|
has = True
|
|
174
|
-
|
|
178
|
+
|
|
175
179
|
if has:
|
|
176
180
|
results.append(element)
|
|
177
|
-
|
|
181
|
+
|
|
178
182
|
elif getattr(element, by, None) == value:
|
|
179
183
|
results.append(element)
|
|
180
|
-
|
|
184
|
+
|
|
181
185
|
if element.childs:
|
|
182
186
|
for child in element.childs:
|
|
183
187
|
results.extend(self.getElements(by=by, value=value, element=child))
|
|
184
|
-
|
|
188
|
+
|
|
185
189
|
return results
|
|
186
|
-
|
|
190
|
+
|
|
187
191
|
def querySelector(self, selector: str, element: 'Element' = None) -> 'Element':
|
|
188
192
|
results = self.querySelectorAll(selector=selector, element=element)
|
|
189
193
|
|
|
190
194
|
return results[0] if results else None
|
|
191
|
-
|
|
195
|
+
|
|
192
196
|
def querySelectorAll(self, selector: str, element: 'Element' = None) -> list['Element']:
|
|
193
197
|
element = element or self
|
|
194
198
|
results: list['Element'] = []
|
|
@@ -196,28 +200,28 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
196
200
|
if selector.startswith('.'):
|
|
197
201
|
classes = ' '.join(selector.split('.')).strip()
|
|
198
202
|
return self.getElements(by=GetBy.classes, value=classes)
|
|
199
|
-
|
|
203
|
+
|
|
200
204
|
elif selector.startswith('#'):
|
|
201
205
|
if selector[1:].strip() == element.id:
|
|
202
206
|
results.append(element)
|
|
203
|
-
|
|
207
|
+
|
|
204
208
|
elif selector.startswith('['):
|
|
205
209
|
sel = selector.removeprefix('[').removesuffix(']')
|
|
206
210
|
return self.getElements(by=GetBy.attrs, value=sel)
|
|
207
|
-
|
|
211
|
+
|
|
208
212
|
else:
|
|
209
213
|
if selector.strip() == element.tag:
|
|
210
214
|
results.append(element)
|
|
211
|
-
|
|
215
|
+
|
|
212
216
|
for child in element.childs:
|
|
213
217
|
results.extend(self.querySelectorAll(selector=selector, element=child))
|
|
214
218
|
|
|
215
219
|
return results
|
|
216
|
-
|
|
220
|
+
|
|
217
221
|
@property
|
|
218
222
|
def clone(self):
|
|
219
223
|
from pyweber.core.events import TemplateEvents
|
|
220
|
-
|
|
224
|
+
|
|
221
225
|
element = self
|
|
222
226
|
|
|
223
227
|
cln = Element(
|
|
@@ -240,20 +244,20 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
240
244
|
cln.childs.append(child.clone)
|
|
241
245
|
|
|
242
246
|
return cln
|
|
243
|
-
|
|
247
|
+
|
|
244
248
|
def get_element_methods(self):
|
|
245
249
|
return self.__element_methods
|
|
246
|
-
|
|
250
|
+
|
|
247
251
|
def __set_element_methods(self, method: str, **kwargs):
|
|
248
252
|
self.__element_methods[method] = kwargs
|
|
249
|
-
|
|
253
|
+
|
|
250
254
|
def remove_element_methods(self, method: Any = None):
|
|
251
255
|
if not method:
|
|
252
256
|
self.__element_methods.clear()
|
|
253
257
|
return
|
|
254
|
-
|
|
258
|
+
|
|
255
259
|
self.__element_methods.pop(method, None)
|
|
256
|
-
|
|
260
|
+
|
|
257
261
|
def __render_dynamic_elements(self, childs: ChildElements):
|
|
258
262
|
new_childs: ChildElements = ChildElements(self)
|
|
259
263
|
if childs:
|
|
@@ -261,7 +265,7 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
261
265
|
if isinstance(child, str):
|
|
262
266
|
if not child.startswith('{{') or not child.endswith('}}'):
|
|
263
267
|
raise ValueError("{} must be starts with '{{' and ends with '}}'".format(child))
|
|
264
|
-
|
|
268
|
+
|
|
265
269
|
key = child.removeprefix('{{').removesuffix('}}').strip()
|
|
266
270
|
element = self.kwargs.get(key, None)
|
|
267
271
|
|
|
@@ -270,18 +274,95 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
270
274
|
|
|
271
275
|
elif isinstance(element, ElementConstrutor):
|
|
272
276
|
new_childs.append(element)
|
|
273
|
-
|
|
277
|
+
|
|
274
278
|
elif isinstance(child, ElementConstrutor):
|
|
275
279
|
new_childs.append(child)
|
|
276
|
-
|
|
280
|
+
|
|
277
281
|
else:
|
|
278
282
|
raise TypeError(f'all childs must be str or Element instances, but got {type(child).__name__}')
|
|
279
|
-
|
|
283
|
+
|
|
280
284
|
return new_childs
|
|
281
285
|
|
|
286
|
+
@classmethod
|
|
287
|
+
def from_html(cls, html: str, include_uuid: bool = True, **kwargs):
|
|
288
|
+
HtmlElement = fromstring(html.strip())
|
|
289
|
+
return cls._create_element(HTMLElement=HtmlElement, parent=None, include_uuid=include_uuid, **kwargs)
|
|
290
|
+
|
|
291
|
+
@classmethod
|
|
292
|
+
def _create_element(cls, HTMLElement, parent=None, include_uuid: bool = True, **kwargs):
|
|
293
|
+
|
|
294
|
+
def gettail(val):
|
|
295
|
+
try: return val.strip()
|
|
296
|
+
except: return val
|
|
297
|
+
|
|
298
|
+
name = 'comment' if isinstance(HTMLElement, HTMLPARSER.HtmlComment) else HTMLElement.tag
|
|
299
|
+
attrib = HTMLElement.attrib
|
|
300
|
+
|
|
301
|
+
id_ = cls.render_dynamic_values(attrib.pop('id', None), **kwargs)
|
|
302
|
+
class_str = cls.render_dynamic_values(attrib.pop('class', None), **kwargs)
|
|
303
|
+
classes = class_str.split() if class_str else []
|
|
304
|
+
|
|
305
|
+
style_str = cls.render_dynamic_values(attrib.pop('style', None), **kwargs)
|
|
306
|
+
style_dict = {}
|
|
307
|
+
if style_str:
|
|
308
|
+
for pair in [s.strip() for s in style_str.split(';') if s.strip()]:
|
|
309
|
+
if ':' in pair:
|
|
310
|
+
k, v = pair.split(':', 1)
|
|
311
|
+
style_dict[k.strip()] = v.strip()
|
|
312
|
+
|
|
313
|
+
uuid = attrib.pop('uuid', None)
|
|
314
|
+
value = cls.render_dynamic_values(attrib.pop('value', None), **kwargs)
|
|
315
|
+
|
|
316
|
+
content = cls.render_dynamic_values(HTMLElement.text or None, **kwargs)
|
|
317
|
+
|
|
318
|
+
events_dict = {k[1:]: attrib.pop(k) for k in list(attrib) if k.startswith('_on')}
|
|
319
|
+
event_obj = TemplateEvents()
|
|
320
|
+
for key, event in events_dict.items():
|
|
321
|
+
if hasattr(event_obj, key): setattr(event_obj, key, event)
|
|
322
|
+
|
|
323
|
+
element = cls(
|
|
324
|
+
tag=name,
|
|
325
|
+
id=id_,
|
|
326
|
+
classes=classes,
|
|
327
|
+
value=value,
|
|
328
|
+
content=content,
|
|
329
|
+
events=event_obj,
|
|
330
|
+
style=style_dict,
|
|
331
|
+
attrs=dict(attrib),
|
|
332
|
+
childs=None,
|
|
333
|
+
sanitize=False,
|
|
334
|
+
files=[],
|
|
335
|
+
include_uuid=include_uuid,
|
|
336
|
+
**kwargs
|
|
337
|
+
)
|
|
338
|
+
element.parent = parent
|
|
339
|
+
element.uuid = uuid
|
|
340
|
+
|
|
341
|
+
if element.tag == 'select' and value:
|
|
342
|
+
__xyza = value
|
|
343
|
+
|
|
344
|
+
for child in HTMLElement.getchildren():
|
|
345
|
+
child_el = cls._create_element(
|
|
346
|
+
HTMLElement=child,
|
|
347
|
+
parent=element,
|
|
348
|
+
include_uuid=include_uuid,
|
|
349
|
+
**kwargs
|
|
350
|
+
)
|
|
351
|
+
element.childs.append(child_el)
|
|
352
|
+
get_tail = gettail(child.tail)
|
|
353
|
+
|
|
354
|
+
if get_tail:
|
|
355
|
+
uuid_child = "{{" + child_el.uuid + "}}"
|
|
356
|
+
element.content = (element.content or '') + f"{uuid_child} {get_tail}"
|
|
357
|
+
|
|
358
|
+
if element.tag == 'select' and element.childs:
|
|
359
|
+
try: element.value = __xyza
|
|
360
|
+
except: pass
|
|
361
|
+
return element
|
|
362
|
+
|
|
282
363
|
def update(self):
|
|
283
364
|
raise NotImplementedError
|
|
284
|
-
|
|
365
|
+
|
|
285
366
|
def __deepy_clone(self, obj):
|
|
286
367
|
if isinstance(obj, list):
|
|
287
368
|
return [self.__deepy_clone(item) for item in obj]
|
|
@@ -289,6 +370,6 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
289
370
|
return {chave: self.__deepy_clone(valor) for chave, valor in obj.items()}
|
|
290
371
|
else:
|
|
291
372
|
return obj
|
|
292
|
-
|
|
373
|
+
|
|
293
374
|
def __str__(self):
|
|
294
|
-
return self.to_html()
|
|
375
|
+
return self.to_html()
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import re
|
|
3
2
|
from uuid import uuid4
|
|
4
|
-
import lxml.html as HTMLPARSER
|
|
5
|
-
from lxml.etree import Element as LXML_Element
|
|
6
|
-
from pyweber.core.events import TemplateEvents
|
|
7
3
|
from pyweber.core.element import Element
|
|
8
4
|
from pyweber.utils.loads import LoadStaticFiles
|
|
9
5
|
from pyweber.config.config import config
|
|
@@ -77,13 +73,11 @@ class Template: # pragma: no cover
|
|
|
77
73
|
return str(config['app'].get('icon'))
|
|
78
74
|
|
|
79
75
|
def parse_html(self, html: str = None):
|
|
80
|
-
if not html:
|
|
81
|
-
html = self.__template
|
|
82
|
-
|
|
76
|
+
if not html: html = self.__template
|
|
83
77
|
return self.__inject_default_elements(root=self.__parse_html(html=html))
|
|
84
78
|
|
|
85
79
|
def build_html(self, include_doctype: bool = True):
|
|
86
|
-
html = self.root.to_html(
|
|
80
|
+
html = self.root.to_html()
|
|
87
81
|
|
|
88
82
|
if include_doctype:
|
|
89
83
|
html = f'<!DOCTYPE html>\n{html}'
|
|
@@ -112,94 +106,13 @@ class Template: # pragma: no cover
|
|
|
112
106
|
html = f'<body>{html}</body>'
|
|
113
107
|
html = f'<html>{html}</html>'
|
|
114
108
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if root.find(path='head') is None:
|
|
118
|
-
root.insert(0, LXML_Element('head'))
|
|
119
|
-
|
|
120
|
-
return self.__create_element(HTMLElement=root)
|
|
121
|
-
|
|
122
|
-
def __create_element(self, HTMLElement: HTMLPARSER.HtmlElement, parent: Element = None):
|
|
123
|
-
def gettail(html_element: str | None):
|
|
124
|
-
try:
|
|
125
|
-
return html_element.strip()
|
|
126
|
-
except:
|
|
127
|
-
return html_element
|
|
128
|
-
|
|
129
|
-
if isinstance(HTMLElement, HTMLPARSER.HtmlComment):
|
|
130
|
-
name = 'comment'
|
|
131
|
-
else:
|
|
132
|
-
name = HTMLElement.tag
|
|
133
|
-
|
|
134
|
-
id = self.__render_dynamic_values(content=HTMLElement.attrib.pop('id', None), include_uuid=self.__include_uuid)
|
|
135
|
-
|
|
136
|
-
class_str = self.__render_dynamic_values(content=HTMLElement.attrib.pop('class', None), include_uuid=self.__include_uuid)
|
|
137
|
-
classes = class_str.split() if class_str else []
|
|
138
|
-
|
|
139
|
-
style_str: str = self.__render_dynamic_values(content=HTMLElement.attrib.pop('style', None), include_uuid=self.__include_uuid)
|
|
140
|
-
style_dict = {}
|
|
141
|
-
|
|
142
|
-
if style_str:
|
|
143
|
-
style_pair = [s.strip() for s in style_str.split(';') if s.strip()]
|
|
144
|
-
|
|
145
|
-
for pair in style_pair:
|
|
146
|
-
if ':' in pair:
|
|
147
|
-
key, value = pair.split(':', 1)
|
|
148
|
-
style_dict[key.strip()] = value.strip()
|
|
149
|
-
|
|
150
|
-
parent = parent
|
|
151
|
-
uuid = HTMLElement.attrib.pop('uuid', None)
|
|
152
|
-
value = self.__render_dynamic_values(content=HTMLElement.attrib.pop('value', None), include_uuid=self.__include_uuid)
|
|
153
|
-
content: str = self.__render_dynamic_values(content=HTMLElement.text if HTMLElement.text else None, include_uuid=self.__include_uuid)
|
|
154
|
-
events_dict = {key[1:]: HTMLElement.attrib.pop(key) for key in HTMLElement.attrib if key.startswith('_on')}
|
|
155
|
-
childrens: list[HTMLPARSER.HtmlElement] = HTMLElement.getchildren()
|
|
156
|
-
|
|
157
|
-
event_obj = TemplateEvents()
|
|
158
|
-
for key, event in events_dict.items():
|
|
159
|
-
if hasattr(event_obj, key):
|
|
160
|
-
setattr(event_obj, key, event)
|
|
161
|
-
|
|
162
|
-
element = Element(
|
|
163
|
-
tag=name,
|
|
164
|
-
id=id,
|
|
165
|
-
classes=classes,
|
|
166
|
-
value=value,
|
|
167
|
-
content=content,
|
|
168
|
-
events=event_obj,
|
|
169
|
-
style=style_dict,
|
|
170
|
-
attrs=dict(HTMLElement.attrib),
|
|
171
|
-
**self.kwargs
|
|
172
|
-
)
|
|
173
|
-
element.parent = parent
|
|
174
|
-
element.template = self
|
|
175
|
-
element.uuid = uuid
|
|
176
|
-
|
|
177
|
-
if parent:
|
|
178
|
-
if gettail(HTMLElement.tail):
|
|
179
|
-
parent.content = parent.content or ''
|
|
180
|
-
parent.content += f"{{{element.uuid}}} {gettail(HTMLElement.tail)}"
|
|
109
|
+
element = Element.from_html(html=html, include_uuid=self.__include_uuid, **self.kwargs)
|
|
181
110
|
|
|
182
|
-
|
|
183
|
-
element.childs.
|
|
111
|
+
if element.tag == 'html':
|
|
112
|
+
if not element.querySelector('head'): element.childs.insert(0, Element('head'))
|
|
184
113
|
|
|
185
114
|
return element
|
|
186
115
|
|
|
187
|
-
def __render_dynamic_values(self, content: str, include_uuid: bool = True):
|
|
188
|
-
if content:
|
|
189
|
-
pattern = r'\{\{(.*?)\}\}'
|
|
190
|
-
result = re.findall(pattern, content)
|
|
191
|
-
|
|
192
|
-
if result:
|
|
193
|
-
for r in result:
|
|
194
|
-
value = self.kwargs.get(r.strip(), None)
|
|
195
|
-
if value is not None:
|
|
196
|
-
if isinstance(value, Element):
|
|
197
|
-
value = self.to_html(element=value, include_uuid=include_uuid)
|
|
198
|
-
|
|
199
|
-
content = content.replace("{{" + r + "}}", str(value))
|
|
200
|
-
|
|
201
|
-
return content
|
|
202
|
-
|
|
203
116
|
def __read_file(self, file_path: str) -> str:
|
|
204
117
|
from pyweber.models.error_pages import ErrorPages
|
|
205
118
|
if file_path.endswith('.html'):
|
|
@@ -16,6 +16,7 @@ from pyweber.utils.types import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
from pyweber.models.file import File
|
|
19
|
+
from questionary import checkbox
|
|
19
20
|
|
|
20
21
|
if TYPE_CHECKING: # pragma: no cover
|
|
21
22
|
from pyweber.core.template import Template
|
|
@@ -68,8 +69,10 @@ class ElementConstrutor: # pragma: no cover
|
|
|
68
69
|
events: TemplateEvents,
|
|
69
70
|
sanitize: bool,
|
|
70
71
|
files: list[File],
|
|
72
|
+
include_uuid: bool,
|
|
71
73
|
**kwargs: str
|
|
72
74
|
):
|
|
75
|
+
self.include_uuid = include_uuid
|
|
73
76
|
self.sanitize = sanitize
|
|
74
77
|
self.kwargs = kwargs
|
|
75
78
|
self.tag = tag
|
|
@@ -147,9 +150,7 @@ class ElementConstrutor: # pragma: no cover
|
|
|
147
150
|
|
|
148
151
|
@uuid.setter
|
|
149
152
|
def uuid(self, value: str):
|
|
150
|
-
if not value:
|
|
151
|
-
self.__uuid = str(uuid4())
|
|
152
|
-
return
|
|
153
|
+
if not value: value = str(uuid4())
|
|
153
154
|
|
|
154
155
|
self.__uuid = value.strip()
|
|
155
156
|
|
|
@@ -347,16 +348,21 @@ class ElementConstrutor: # pragma: no cover
|
|
|
347
348
|
if self.tag == 'textarea':
|
|
348
349
|
self.content = value
|
|
349
350
|
|
|
350
|
-
|
|
351
|
+
elif self.tag == 'select':
|
|
351
352
|
self.__value = None
|
|
352
353
|
if hasattr(self, 'childs') and self.childs:
|
|
353
354
|
for child in self.childs:
|
|
354
355
|
if child.tag == 'option':
|
|
355
|
-
if child.value == value:
|
|
356
|
+
if child.value == value or child.content == value:
|
|
356
357
|
child.set_attr('selected', '')
|
|
357
358
|
else:
|
|
358
359
|
child.remove_attr('selected')
|
|
359
360
|
|
|
361
|
+
elif self.get_attr('type', None) == 'checkbox':
|
|
362
|
+
self.__value = None
|
|
363
|
+
if value == 'on':
|
|
364
|
+
self.set_attr('checked', '')
|
|
365
|
+
|
|
360
366
|
@property
|
|
361
367
|
def events(self):
|
|
362
368
|
return self.__events
|
|
@@ -383,7 +389,7 @@ class ElementConstrutor: # pragma: no cover
|
|
|
383
389
|
|
|
384
390
|
setattr(self.__events, event_type.value, None)
|
|
385
391
|
|
|
386
|
-
def to_html(self, element: '
|
|
392
|
+
def to_html(self, element: 'Element' = None, indent: int = 0):
|
|
387
393
|
if not element:
|
|
388
394
|
element = self
|
|
389
395
|
|
|
@@ -391,7 +397,7 @@ class ElementConstrutor: # pragma: no cover
|
|
|
391
397
|
raise TypeError(f'element must be an Element instances, but got {type(element).__name__}')
|
|
392
398
|
|
|
393
399
|
indentation = ' ' * indent
|
|
394
|
-
uuid_attribute = f' uuid="{element.uuid}"' if include_uuid else ""
|
|
400
|
+
uuid_attribute = f' uuid="{element.uuid}"' if self.include_uuid else ""
|
|
395
401
|
html = f'{indentation}<{element.tag}{uuid_attribute}' if element.tag != 'comment' else f'{indentation}<!--'
|
|
396
402
|
|
|
397
403
|
if element.id:
|
|
@@ -425,15 +431,15 @@ class ElementConstrutor: # pragma: no cover
|
|
|
425
431
|
if element.tag != 'comment':
|
|
426
432
|
html += '>'
|
|
427
433
|
|
|
428
|
-
final_content = str((self.
|
|
434
|
+
final_content = str((self.render_dynamic_values(content=element.content, **self.kwargs) or ''))
|
|
429
435
|
has_children = bool(element.childs)
|
|
430
436
|
|
|
431
437
|
if has_children or '\n' in final_content:
|
|
432
438
|
html += '\n'
|
|
433
439
|
|
|
434
440
|
for child in element.childs:
|
|
435
|
-
child_html = self.to_html(child, indent + 4
|
|
436
|
-
uuid_placeholder =
|
|
441
|
+
child_html = self.to_html(child, indent + 4)
|
|
442
|
+
uuid_placeholder = "{{" + child.uuid + "}}"
|
|
437
443
|
|
|
438
444
|
if uuid_placeholder in final_content:
|
|
439
445
|
final_content: str = final_content.replace(uuid_placeholder, child_html)
|
|
@@ -450,8 +456,8 @@ class ElementConstrutor: # pragma: no cover
|
|
|
450
456
|
|
|
451
457
|
return html
|
|
452
458
|
|
|
453
|
-
|
|
454
|
-
|
|
459
|
+
@classmethod
|
|
460
|
+
def render_dynamic_values(self, content: str, **kwargs):
|
|
455
461
|
|
|
456
462
|
if content:
|
|
457
463
|
pattern = r'\{\{(.*?)\}\}'
|
|
@@ -459,16 +465,15 @@ class ElementConstrutor: # pragma: no cover
|
|
|
459
465
|
|
|
460
466
|
if result:
|
|
461
467
|
for r in result:
|
|
462
|
-
value =
|
|
468
|
+
value = kwargs.get(r.strip(), None)
|
|
463
469
|
if value is not None:
|
|
464
470
|
if isinstance(value, ElementConstrutor):
|
|
465
|
-
value = self.to_html(element=value
|
|
471
|
+
value = self.to_html(element=value)
|
|
466
472
|
|
|
467
473
|
content = content.replace("{{" + r + "}}", str(value))
|
|
468
474
|
|
|
469
475
|
return content
|
|
470
476
|
|
|
471
|
-
|
|
472
477
|
def create_event_id(self, event: Union[Callable, str], type: str, element_id: str = None):
|
|
473
478
|
from pyweber.core.events import EventBook
|
|
474
479
|
|
|
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
|
|
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.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/apple-touch-icon.png
RENAMED
|
File without changes
|
{pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/favicon-16x16.png
RENAMED
|
File without changes
|
{pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/favicon-32x32.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/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
|
|
File without changes
|
{pyweber-1.2.0.dev20260424 → pyweber-1.2.0.dev20260425}/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
|