pyweber 1.2.0.dev20260423__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.dev20260423/pyweber.egg-info → pyweber-1.2.0.dev20260425}/PKG-INFO +1 -1
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyproject.toml +1 -1
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/core/element.py +130 -49
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/core/template.py +45 -130
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/element.py +21 -16
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425/pyweber.egg-info}/PKG-INFO +1 -1
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/LICENSE +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/MANIFEST.in +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/README.md +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/__init__.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/admin/index.html +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/admin/src/script.js +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/admin/src/style.css +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/cli/__init__.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/cli/commands.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/components/__init__.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/components/form.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/components/general.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/components/input.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/config/__init__.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/config/config.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/connection/__init__.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/connection/http.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/connection/reload.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/connection/selector.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/connection/session.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/connection/websocket.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/core/__init__.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/core/events.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/core/window.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/__init__.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/cookies.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/create_app.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/error_pages.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/field.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/field_storage.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/file.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/file_stream.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/headers.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/middleware.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/openapi.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/request.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/response.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/routes.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/run.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/strem_stats.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/task_manager.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/template_diff.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/ws_message.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/pyweber/__init__.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/pyweber/pyweber.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/.gitignore +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/config.toml +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/css.css +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/docs.html +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/android-chrome-192x192.png +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/android-chrome-512x512.png +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/apple-touch-icon.png +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/favicon-16x16.png +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/favicon-32x32.png +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/favicon.ico +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/pyweber.png +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/site.webmanifest +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/handlers.js +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/html.html +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/html401.html +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/html404.html +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/html500.html +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/js.js +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/loading.html +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/main.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/pyweber.css +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/update.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/utils/__init__.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/utils/exceptions.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/utils/loads.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/utils/types.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/utils/utils.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/SOURCES.txt +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/dependency_links.txt +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/entry_points.txt +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/requires.txt +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/top_level.txt +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/setup.cfg +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/tests/test_config.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/tests/test_cookies.py +0 -0
- {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/tests/test_response.py +0 -0
- {pyweber-1.2.0.dev20260423 → 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,8 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from uuid import uuid4
|
|
3
|
-
import lxml.html as HTMLPARSER
|
|
4
|
-
from lxml.etree import Element as LXML_Element
|
|
5
|
-
from pyweber.core.events import TemplateEvents
|
|
6
3
|
from pyweber.core.element import Element
|
|
7
4
|
from pyweber.utils.loads import LoadStaticFiles
|
|
8
5
|
from pyweber.config.config import config
|
|
@@ -10,6 +7,7 @@ from pyweber.utils.types import HTTPStatusCode, GetBy
|
|
|
10
7
|
|
|
11
8
|
class Template: # pragma: no cover
|
|
12
9
|
def __init__(self, template: str, status_code: int = 200, title: str = None, include_uuid: bool = True, **kwargs):
|
|
10
|
+
self.__include_uuid = include_uuid
|
|
13
11
|
self.__template = self.__read_file(file_path=template)
|
|
14
12
|
self.kwargs = kwargs
|
|
15
13
|
self.data = None
|
|
@@ -17,39 +15,38 @@ class Template: # pragma: no cover
|
|
|
17
15
|
self.__icon: str = self.get_icon()
|
|
18
16
|
self.title = title
|
|
19
17
|
self.__root = self.parse_html()
|
|
20
|
-
self.__include_uuid = include_uuid
|
|
21
18
|
|
|
22
19
|
@property
|
|
23
20
|
def template(self):
|
|
24
21
|
return self.__template
|
|
25
|
-
|
|
22
|
+
|
|
26
23
|
@property
|
|
27
24
|
def root(self):
|
|
28
25
|
return self.__root
|
|
29
|
-
|
|
26
|
+
|
|
30
27
|
@root.setter
|
|
31
28
|
def root(self, value: Element):
|
|
32
29
|
if value.tag != 'html':
|
|
33
30
|
raise ValueError('This Element is not valid to root. Please add the Html Element')
|
|
34
|
-
|
|
31
|
+
|
|
35
32
|
self.__root = value
|
|
36
|
-
|
|
33
|
+
|
|
37
34
|
@property
|
|
38
35
|
def status_code(self):
|
|
39
36
|
return self.__status_code
|
|
40
|
-
|
|
37
|
+
|
|
41
38
|
@status_code.setter
|
|
42
39
|
def status_code(self, code: int):
|
|
43
40
|
if code not in HTTPStatusCode.code_list():
|
|
44
41
|
raise ValueError(f'The code {code} is not a HttpStatusCode')
|
|
45
|
-
|
|
42
|
+
|
|
46
43
|
self.__status_code = code
|
|
47
44
|
|
|
48
45
|
@property
|
|
49
46
|
def events(self):
|
|
50
47
|
from pyweber.core.events import EventBook
|
|
51
48
|
return EventBook
|
|
52
|
-
|
|
49
|
+
|
|
53
50
|
@property
|
|
54
51
|
def title(self): return self.__title
|
|
55
52
|
|
|
@@ -62,10 +59,10 @@ class Template: # pragma: no cover
|
|
|
62
59
|
|
|
63
60
|
if title:
|
|
64
61
|
title.content = value if value else title.content
|
|
65
|
-
|
|
62
|
+
|
|
66
63
|
@property
|
|
67
64
|
def head(self): return self.root.querySelector('head')
|
|
68
|
-
|
|
65
|
+
|
|
69
66
|
@property
|
|
70
67
|
def body(self): return self.root.querySelector('body')
|
|
71
68
|
|
|
@@ -74,29 +71,27 @@ class Template: # pragma: no cover
|
|
|
74
71
|
|
|
75
72
|
def get_icon(self):
|
|
76
73
|
return str(config['app'].get('icon'))
|
|
77
|
-
|
|
74
|
+
|
|
78
75
|
def parse_html(self, html: str = None):
|
|
79
|
-
if not html:
|
|
80
|
-
html = self.__template
|
|
81
|
-
|
|
76
|
+
if not html: html = self.__template
|
|
82
77
|
return self.__inject_default_elements(root=self.__parse_html(html=html))
|
|
83
78
|
|
|
84
79
|
def build_html(self, include_doctype: bool = True):
|
|
85
|
-
html = self.root.to_html(
|
|
86
|
-
|
|
80
|
+
html = self.root.to_html()
|
|
81
|
+
|
|
87
82
|
if include_doctype:
|
|
88
83
|
html = f'<!DOCTYPE html>\n{html}'
|
|
89
|
-
|
|
84
|
+
|
|
90
85
|
return html
|
|
91
|
-
|
|
86
|
+
|
|
92
87
|
def getElement(self, by: GetBy, value: str, element: Element = None):
|
|
93
88
|
if not element: element = self.root
|
|
94
89
|
return element.getElement(by=by, value=value)
|
|
95
|
-
|
|
90
|
+
|
|
96
91
|
def getElements(self, by: GetBy, value: str, element: Element = None):
|
|
97
92
|
if not element: element = self.root
|
|
98
93
|
return element.getElements(by=by, value=value)
|
|
99
|
-
|
|
94
|
+
|
|
100
95
|
def querySelector(self, selector: str, element: Element = None):
|
|
101
96
|
if element is None: element = self.__root
|
|
102
97
|
return element.querySelector(selector=selector)
|
|
@@ -104,145 +99,65 @@ class Template: # pragma: no cover
|
|
|
104
99
|
def querySelectorAll(self, selector: str, element: Element = None) -> list[Element]:
|
|
105
100
|
if element is None: element = self.__root
|
|
106
101
|
return element.querySelectorAll(selector=selector)
|
|
107
|
-
|
|
102
|
+
|
|
108
103
|
def __parse_html(self, html: str) -> Element:
|
|
109
104
|
if not html.replace('<!DOCTYPE html>', '').strip().startswith('<html'):
|
|
110
105
|
if not html.startswith('<body'):
|
|
111
106
|
html = f'<body>{html}</body>'
|
|
112
107
|
html = f'<html>{html}</html>'
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return self.__create_element(HTMLElement=root)
|
|
120
|
-
|
|
121
|
-
def __create_element(self, HTMLElement: HTMLPARSER.HtmlElement, parent: Element = None):
|
|
122
|
-
def gettail(html_element: str | None):
|
|
123
|
-
try:
|
|
124
|
-
return html_element.strip()
|
|
125
|
-
except:
|
|
126
|
-
return html_element
|
|
127
|
-
|
|
128
|
-
if isinstance(HTMLElement, HTMLPARSER.HtmlComment):
|
|
129
|
-
name = 'comment'
|
|
130
|
-
else:
|
|
131
|
-
name = HTMLElement.tag
|
|
132
|
-
|
|
133
|
-
id = self.__render_dynamic_values(content=HTMLElement.attrib.pop('id', None))
|
|
134
|
-
|
|
135
|
-
class_str = self.__render_dynamic_values(content=HTMLElement.attrib.pop('class', None))
|
|
136
|
-
classes = class_str.split() if class_str else []
|
|
137
|
-
|
|
138
|
-
style_str: str = self.__render_dynamic_values(content=HTMLElement.attrib.pop('style', None))
|
|
139
|
-
style_dict = {}
|
|
140
|
-
|
|
141
|
-
if style_str:
|
|
142
|
-
style_pair = [s.strip() for s in style_str.split(';') if s.strip()]
|
|
143
|
-
|
|
144
|
-
for pair in style_pair:
|
|
145
|
-
if ':' in pair:
|
|
146
|
-
key, value = pair.split(':', 1)
|
|
147
|
-
style_dict[key.strip()] = value.strip()
|
|
148
|
-
|
|
149
|
-
parent = parent
|
|
150
|
-
uuid = HTMLElement.attrib.pop('uuid', None)
|
|
151
|
-
value = self.__render_dynamic_values(content=HTMLElement.attrib.pop('value', None))
|
|
152
|
-
content: str = self.__render_dynamic_values(content=HTMLElement.text if HTMLElement.text else None)
|
|
153
|
-
events_dict = {key[1:]: HTMLElement.attrib.pop(key) for key in HTMLElement.attrib if key.startswith('_on')}
|
|
154
|
-
childrens: list[HTMLPARSER.HtmlElement] = HTMLElement.getchildren()
|
|
155
|
-
|
|
156
|
-
event_obj = TemplateEvents()
|
|
157
|
-
for key, event in events_dict.items():
|
|
158
|
-
if hasattr(event_obj, key):
|
|
159
|
-
setattr(event_obj, key, event)
|
|
160
|
-
|
|
161
|
-
element = Element(
|
|
162
|
-
tag=name,
|
|
163
|
-
id=id,
|
|
164
|
-
classes=classes,
|
|
165
|
-
value=value,
|
|
166
|
-
content=content,
|
|
167
|
-
events=event_obj,
|
|
168
|
-
style=style_dict,
|
|
169
|
-
attrs=dict(HTMLElement.attrib),
|
|
170
|
-
**self.kwargs
|
|
171
|
-
)
|
|
172
|
-
element.parent = parent
|
|
173
|
-
element.template = self
|
|
174
|
-
element.uuid = uuid
|
|
175
|
-
|
|
176
|
-
if parent:
|
|
177
|
-
if gettail(HTMLElement.tail):
|
|
178
|
-
parent.content = parent.content or ''
|
|
179
|
-
parent.content += f"{{{element.uuid}}} {gettail(HTMLElement.tail)}"
|
|
180
|
-
|
|
181
|
-
for child in childrens:
|
|
182
|
-
element.childs.append(self.__create_element(child, element))
|
|
183
|
-
|
|
108
|
+
|
|
109
|
+
element = Element.from_html(html=html, include_uuid=self.__include_uuid, **self.kwargs)
|
|
110
|
+
|
|
111
|
+
if element.tag == 'html':
|
|
112
|
+
if not element.querySelector('head'): element.childs.insert(0, Element('head'))
|
|
113
|
+
|
|
184
114
|
return element
|
|
185
|
-
|
|
186
|
-
def __render_dynamic_values(self, content: str):
|
|
187
|
-
if content:
|
|
188
|
-
begin = content.find('{{')
|
|
189
|
-
if begin != -1:
|
|
190
|
-
end = content.find('}}')
|
|
191
|
-
key = content[begin:end+2].removeprefix('{{').removesuffix('}}').strip()
|
|
192
|
-
new_content = self.kwargs.get(key, None)
|
|
193
|
-
|
|
194
|
-
if new_content:
|
|
195
|
-
if isinstance(new_content, Element):
|
|
196
|
-
new_content = self.build_html(element=new_content)
|
|
197
|
-
content = content.replace(content[begin:end+2], str(new_content))
|
|
198
|
-
|
|
199
|
-
return content
|
|
200
115
|
|
|
201
116
|
def __read_file(self, file_path: str) -> str:
|
|
202
117
|
from pyweber.models.error_pages import ErrorPages
|
|
203
118
|
if file_path.endswith('.html'):
|
|
204
119
|
path = os.path.join('templates', file_path) if not os.path.isfile(file_path) else file_path
|
|
205
|
-
|
|
120
|
+
|
|
206
121
|
try:
|
|
207
122
|
return LoadStaticFiles(path=path).load
|
|
208
|
-
|
|
123
|
+
|
|
209
124
|
except FileNotFoundError:
|
|
210
125
|
return ErrorPages().page_server_error.build_html().replace(
|
|
211
126
|
"{{error}}",
|
|
212
127
|
f'{path} not found, please include on templates file'
|
|
213
128
|
)
|
|
214
|
-
|
|
129
|
+
|
|
215
130
|
return file_path
|
|
216
|
-
|
|
131
|
+
|
|
217
132
|
def __create_default_element(self, *args, **kwargs):
|
|
218
133
|
return Element(*args, **kwargs)
|
|
219
|
-
|
|
134
|
+
|
|
220
135
|
def __inject_default_elements(self, root: Element):
|
|
221
136
|
has_websocket_script, has_icon, has_description, has_css, has_keywords, has_title = False, False, False, False, False, False
|
|
222
137
|
for child in root.childs[0].childs:
|
|
223
138
|
if child.tag == 'script' and child.get_attr('src', '').startswith('/_pyweber/static/') and child.get_attr('src', '').endswith('/.js'):
|
|
224
139
|
has_websocket_script = True
|
|
225
|
-
|
|
140
|
+
|
|
226
141
|
elif child.tag == 'link':
|
|
227
142
|
if 'icon' in list(child.attrs.values()):
|
|
228
143
|
has_icon = True
|
|
229
|
-
|
|
144
|
+
|
|
230
145
|
elif child.get_attr('rel') == 'stylesheet' and child.get_attr('href', '').startswith('/_pyweber/static/') and child.get_attr('href', '').endswith('/.css'):
|
|
231
146
|
has_css = True
|
|
232
|
-
|
|
147
|
+
|
|
233
148
|
elif child.tag == 'meta':
|
|
234
149
|
if 'description' in list(child.attrs.values()):
|
|
235
150
|
has_description = True
|
|
236
|
-
|
|
151
|
+
|
|
237
152
|
elif 'keywords' in list(child.attrs.values()):
|
|
238
153
|
has_keywords = True
|
|
239
|
-
|
|
154
|
+
|
|
240
155
|
elif child.tag == 'title':
|
|
241
156
|
has_title = child
|
|
242
|
-
|
|
157
|
+
|
|
243
158
|
if has_websocket_script and has_icon and has_css and has_description and has_keywords and has_title:
|
|
244
159
|
break
|
|
245
|
-
|
|
160
|
+
|
|
246
161
|
if not has_websocket_script:
|
|
247
162
|
# insert websockets port if exists
|
|
248
163
|
disable_ws = os.environ.get('PYWEBER_DISABLE_WS', False)
|
|
@@ -256,7 +171,7 @@ class Template: # pragma: no cover
|
|
|
256
171
|
)
|
|
257
172
|
]
|
|
258
173
|
)
|
|
259
|
-
|
|
174
|
+
|
|
260
175
|
if not has_icon:
|
|
261
176
|
root.childs[0].childs.append(
|
|
262
177
|
self.__create_default_element(
|
|
@@ -264,7 +179,7 @@ class Template: # pragma: no cover
|
|
|
264
179
|
attrs={'rel': 'icon', 'href': f'{self.__icon.strip()}'.replace('\\', '/')}
|
|
265
180
|
)
|
|
266
181
|
)
|
|
267
|
-
|
|
182
|
+
|
|
268
183
|
if not has_css:
|
|
269
184
|
root.childs[0].childs.insert(
|
|
270
185
|
1,
|
|
@@ -273,7 +188,7 @@ class Template: # pragma: no cover
|
|
|
273
188
|
attrs={'rel': 'stylesheet', 'href': f'/_pyweber/static/{str(uuid4())}/.css'}
|
|
274
189
|
)
|
|
275
190
|
)
|
|
276
|
-
|
|
191
|
+
|
|
277
192
|
if not has_description:
|
|
278
193
|
root.childs[0].childs.insert(
|
|
279
194
|
0,
|
|
@@ -282,7 +197,7 @@ class Template: # pragma: no cover
|
|
|
282
197
|
attrs={'name': 'description', 'content': config['app'].get('description')}
|
|
283
198
|
)
|
|
284
199
|
)
|
|
285
|
-
|
|
200
|
+
|
|
286
201
|
if not has_keywords:
|
|
287
202
|
root.childs[0].childs.insert(
|
|
288
203
|
0,
|
|
@@ -291,10 +206,10 @@ class Template: # pragma: no cover
|
|
|
291
206
|
attrs={'name': 'keywords', 'content': ', '.join(config['app'].get('keywords', []))}
|
|
292
207
|
)
|
|
293
208
|
)
|
|
294
|
-
|
|
209
|
+
|
|
295
210
|
if isinstance(has_title, Element):
|
|
296
211
|
has_title.content = self.title if self.title else has_title.content
|
|
297
|
-
|
|
212
|
+
|
|
298
213
|
else:
|
|
299
214
|
root.childs[0].childs.append(
|
|
300
215
|
self.__create_default_element(
|
|
@@ -304,7 +219,7 @@ class Template: # pragma: no cover
|
|
|
304
219
|
)
|
|
305
220
|
|
|
306
221
|
return root
|
|
307
|
-
|
|
222
|
+
|
|
308
223
|
def clone(self):
|
|
309
224
|
tpl = Template(
|
|
310
225
|
template=self.template,
|
|
@@ -315,4 +230,4 @@ class Template: # pragma: no cover
|
|
|
315
230
|
tpl.data = self.data
|
|
316
231
|
tpl.root = self.root.clone
|
|
317
232
|
|
|
318
|
-
return tpl
|
|
233
|
+
return tpl
|
|
@@ -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,7 +456,8 @@ class ElementConstrutor: # pragma: no cover
|
|
|
450
456
|
|
|
451
457
|
return html
|
|
452
458
|
|
|
453
|
-
|
|
459
|
+
@classmethod
|
|
460
|
+
def render_dynamic_values(self, content: str, **kwargs):
|
|
454
461
|
|
|
455
462
|
if content:
|
|
456
463
|
pattern = r'\{\{(.*?)\}\}'
|
|
@@ -458,17 +465,15 @@ class ElementConstrutor: # pragma: no cover
|
|
|
458
465
|
|
|
459
466
|
if result:
|
|
460
467
|
for r in result:
|
|
461
|
-
value =
|
|
462
|
-
if value:
|
|
463
|
-
|
|
468
|
+
value = kwargs.get(r.strip(), None)
|
|
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.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/apple-touch-icon.png
RENAMED
|
File without changes
|
{pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/favicon-16x16.png
RENAMED
|
File without changes
|
{pyweber-1.2.0.dev20260423 → 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.dev20260423 → 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.dev20260423 → 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
|