pyweber 1.2.0.dev20260425__tar.gz → 1.2.0.dev20260426__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.dev20260425/pyweber.egg-info → pyweber-1.2.0.dev20260426}/PKG-INFO +1 -1
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyproject.toml +1 -1
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/core/element.py +93 -31
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/core/template.py +34 -12
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/cookies.py +8 -8
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/element.py +11 -1
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/response.py +2 -2
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/routes.py +9 -1
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/pyweber/pyweber.py +5 -3
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426/pyweber.egg-info}/PKG-INFO +1 -1
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/LICENSE +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/MANIFEST.in +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/README.md +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/__init__.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/admin/index.html +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/admin/src/script.js +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/admin/src/style.css +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/cli/__init__.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/cli/commands.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/components/__init__.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/components/form.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/components/general.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/components/input.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/config/__init__.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/config/config.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/connection/__init__.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/connection/http.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/connection/reload.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/connection/selector.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/connection/session.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/connection/websocket.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/core/__init__.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/core/events.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/core/window.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/__init__.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/create_app.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/error_pages.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/field.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/field_storage.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/file.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/file_stream.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/headers.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/middleware.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/openapi.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/request.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/run.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/strem_stats.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/task_manager.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/template_diff.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/ws_message.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/pyweber/__init__.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/.gitignore +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/config.toml +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/css.css +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/docs.html +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/android-chrome-192x192.png +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/android-chrome-512x512.png +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/apple-touch-icon.png +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/favicon-16x16.png +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/favicon-32x32.png +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/favicon.ico +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/pyweber.png +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/site.webmanifest +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/handlers.js +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/html.html +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/html401.html +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/html404.html +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/html500.html +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/js.js +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/loading.html +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/main.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/pyweber.css +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/update.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/utils/__init__.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/utils/exceptions.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/utils/loads.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/utils/types.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/utils/utils.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber.egg-info/SOURCES.txt +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber.egg-info/dependency_links.txt +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber.egg-info/entry_points.txt +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber.egg-info/requires.txt +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber.egg-info/top_level.txt +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/setup.cfg +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/tests/test_config.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/tests/test_cookies.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/tests/test_response.py +0 -0
- {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/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.dev20260426"
|
|
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,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from uuid import uuid4
|
|
2
3
|
import lxml.html as HTMLPARSER
|
|
3
4
|
from lxml.html import fromstring
|
|
@@ -10,6 +11,8 @@ from pyweber.models.element import (
|
|
|
10
11
|
ChildElements
|
|
11
12
|
)
|
|
12
13
|
|
|
14
|
+
SEARCH_MODE = Literal['exact', 'regex', 'contains', 'startswith', 'endswith']
|
|
15
|
+
|
|
13
16
|
class Element(ElementConstrutor): # pragma: no cover
|
|
14
17
|
def __init__(
|
|
15
18
|
self,
|
|
@@ -143,12 +146,79 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
143
146
|
def set_selection_range(self, start: int, end: str):
|
|
144
147
|
self.__set_element_methods(method='setSelectionRange', start=start, end=end)
|
|
145
148
|
|
|
146
|
-
def getElement(self, by: GetBy, value: str, element: 'Element' = None) -> 'Element':
|
|
147
|
-
results = self.getElements(by=by, value=value, element=element)
|
|
149
|
+
def getElement(self, by: GetBy, value: str, element: 'Element' = None, search_mode: SEARCH_MODE = 'exact') -> 'Element':
|
|
150
|
+
results = self.getElements(by=by, value=value, element=element, search_mode=search_mode)
|
|
148
151
|
|
|
149
152
|
return results[0] if results else None
|
|
150
153
|
|
|
151
|
-
def getElements(
|
|
154
|
+
def getElements(
|
|
155
|
+
self,
|
|
156
|
+
by: GetBy,
|
|
157
|
+
value: str,
|
|
158
|
+
element: 'Element' = None,
|
|
159
|
+
search_mode: SEARCH_MODE = 'exact'
|
|
160
|
+
) -> list['Element']:
|
|
161
|
+
|
|
162
|
+
def matches(target, val) -> bool:
|
|
163
|
+
if target is None: return False
|
|
164
|
+
|
|
165
|
+
# Lista (classes)
|
|
166
|
+
if isinstance(target, list):
|
|
167
|
+
search_classes = val.split() # classes que o utilizador passou
|
|
168
|
+
match search_mode:
|
|
169
|
+
case 'exact': return set(search_classes) <= set(target)
|
|
170
|
+
case 'regex': return all(any(re.search(cls, t) for t in target) for cls in search_classes)
|
|
171
|
+
case 'contains': return all(any(cls in t for t in target) for cls in search_classes)
|
|
172
|
+
case 'startswith': return all(any(t.startswith(cls) for t in target) for cls in search_classes)
|
|
173
|
+
case 'endswith': return all(any(t.endswith(cls) for t in target) for cls in search_classes)
|
|
174
|
+
|
|
175
|
+
# Dict (attrs, style)
|
|
176
|
+
if isinstance(target, dict):
|
|
177
|
+
conditions = [pair.strip() for pair in val.split(';') if pair.strip()]
|
|
178
|
+
|
|
179
|
+
match search_mode:
|
|
180
|
+
case 'exact':
|
|
181
|
+
for condition in conditions:
|
|
182
|
+
key, _, v = condition.partition(':')
|
|
183
|
+
if not v: key, _, v = condition.partition('=')
|
|
184
|
+
if not v:
|
|
185
|
+
if key.strip() not in target: return False
|
|
186
|
+
elif target.get(key.strip()) != v.strip():
|
|
187
|
+
return False
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
case 'regex':
|
|
191
|
+
return all(
|
|
192
|
+
any(re.search(cond, k) or re.search(cond, str(v)) for k, v in target.items())
|
|
193
|
+
for cond in conditions
|
|
194
|
+
)
|
|
195
|
+
case 'contains':
|
|
196
|
+
return all(
|
|
197
|
+
any(cond in k or cond in str(v) for k, v in target.items())
|
|
198
|
+
for cond in conditions
|
|
199
|
+
)
|
|
200
|
+
case 'startswith':
|
|
201
|
+
return all(
|
|
202
|
+
any(k.startswith(cond) or str(v).startswith(cond) for k, v in target.items())
|
|
203
|
+
for cond in conditions
|
|
204
|
+
)
|
|
205
|
+
case 'endswith':
|
|
206
|
+
return all(
|
|
207
|
+
any(k.endswith(cond) or str(v).endswith(cond) for k, v in target.items())
|
|
208
|
+
for cond in conditions
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# String (id, tag, content, etc)
|
|
212
|
+
target = str(target)
|
|
213
|
+
match search_mode:
|
|
214
|
+
case 'exact': return target == val
|
|
215
|
+
case 'regex': return bool(re.search(val, target))
|
|
216
|
+
case 'contains': return val in target
|
|
217
|
+
case 'startswith': return target.startswith(val)
|
|
218
|
+
case 'endswith': return target.endswith(val)
|
|
219
|
+
|
|
220
|
+
return False
|
|
221
|
+
|
|
152
222
|
element = element or self
|
|
153
223
|
results: list['Element'] = []
|
|
154
224
|
|
|
@@ -156,50 +226,34 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
156
226
|
by: str = by.value
|
|
157
227
|
|
|
158
228
|
if by == 'classes':
|
|
159
|
-
if
|
|
229
|
+
if matches(element.classes, value):
|
|
160
230
|
results.append(element)
|
|
161
231
|
|
|
162
232
|
elif by in ['attrs', 'style']:
|
|
163
|
-
|
|
164
|
-
has = False
|
|
165
|
-
|
|
166
|
-
el: dict[str, str] = getattr(element, by, {})
|
|
167
|
-
for condition in conditions:
|
|
168
|
-
key, _, val = condition.partition(':')
|
|
169
|
-
|
|
170
|
-
if not val: key, _, val = condition.partition('=')
|
|
171
|
-
|
|
172
|
-
if not val:
|
|
173
|
-
if key.strip() in el:
|
|
174
|
-
has = True
|
|
175
|
-
|
|
176
|
-
elif key.strip() in el and el.get(key.strip(), None) == val.strip():
|
|
177
|
-
has = True
|
|
178
|
-
|
|
179
|
-
if has:
|
|
233
|
+
if matches(getattr(element, by, {}), value):
|
|
180
234
|
results.append(element)
|
|
181
235
|
|
|
182
|
-
elif getattr(element, by, None)
|
|
236
|
+
elif matches(getattr(element, by, None), value):
|
|
183
237
|
results.append(element)
|
|
184
238
|
|
|
185
239
|
if element.childs:
|
|
186
240
|
for child in element.childs:
|
|
187
|
-
results.extend(self.getElements(by=by, value=value, element=child))
|
|
241
|
+
results.extend(self.getElements(by=by, value=value, element=child, search_mode=search_mode))
|
|
188
242
|
|
|
189
243
|
return results
|
|
190
244
|
|
|
191
|
-
def querySelector(self, selector: str, element: 'Element' = None) -> 'Element':
|
|
192
|
-
results = self.querySelectorAll(selector=selector, element=element)
|
|
245
|
+
def querySelector(self, selector: str, element: 'Element' = None, search_mode: SEARCH_MODE = 'exact') -> 'Element':
|
|
246
|
+
results = self.querySelectorAll(selector=selector, element=element, search_mode=search_mode)
|
|
193
247
|
|
|
194
248
|
return results[0] if results else None
|
|
195
249
|
|
|
196
|
-
def querySelectorAll(self, selector: str, element: 'Element' = None) -> list['Element']:
|
|
250
|
+
def querySelectorAll(self, selector: str, element: 'Element' = None, search_mode: SEARCH_MODE = 'exact') -> list['Element']:
|
|
197
251
|
element = element or self
|
|
198
252
|
results: list['Element'] = []
|
|
199
253
|
|
|
200
254
|
if selector.startswith('.'):
|
|
201
255
|
classes = ' '.join(selector.split('.')).strip()
|
|
202
|
-
return self.getElements(by=GetBy.classes, value=classes)
|
|
256
|
+
return self.getElements(by=GetBy.classes, value=classes, search_mode=search_mode)
|
|
203
257
|
|
|
204
258
|
elif selector.startswith('#'):
|
|
205
259
|
if selector[1:].strip() == element.id:
|
|
@@ -207,14 +261,14 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
207
261
|
|
|
208
262
|
elif selector.startswith('['):
|
|
209
263
|
sel = selector.removeprefix('[').removesuffix(']')
|
|
210
|
-
return self.getElements(by=GetBy.attrs, value=sel)
|
|
264
|
+
return self.getElements(by=GetBy.attrs, value=sel, search_mode=search_mode)
|
|
211
265
|
|
|
212
266
|
else:
|
|
213
267
|
if selector.strip() == element.tag:
|
|
214
268
|
results.append(element)
|
|
215
269
|
|
|
216
270
|
for child in element.childs:
|
|
217
|
-
results.extend(self.querySelectorAll(selector=selector, element=child))
|
|
271
|
+
results.extend(self.querySelectorAll(selector=selector, element=child, search_mode=search_mode))
|
|
218
272
|
|
|
219
273
|
return results
|
|
220
274
|
|
|
@@ -320,6 +374,11 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
320
374
|
for key, event in events_dict.items():
|
|
321
375
|
if hasattr(event_obj, key): setattr(event_obj, key, event)
|
|
322
376
|
|
|
377
|
+
attrs = {
|
|
378
|
+
cls.render_dynamic_values(c, **kwargs): cls.render_dynamic_values(v, **kwargs)
|
|
379
|
+
for c, v in attrib.items()
|
|
380
|
+
}
|
|
381
|
+
|
|
323
382
|
element = cls(
|
|
324
383
|
tag=name,
|
|
325
384
|
id=id_,
|
|
@@ -328,7 +387,7 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
328
387
|
content=content,
|
|
329
388
|
events=event_obj,
|
|
330
389
|
style=style_dict,
|
|
331
|
-
attrs=
|
|
390
|
+
attrs=attrs,
|
|
332
391
|
childs=None,
|
|
333
392
|
sanitize=False,
|
|
334
393
|
files=[],
|
|
@@ -353,7 +412,10 @@ class Element(ElementConstrutor): # pragma: no cover
|
|
|
353
412
|
|
|
354
413
|
if get_tail:
|
|
355
414
|
uuid_child = "{{" + child_el.uuid + "}}"
|
|
356
|
-
element.content = (
|
|
415
|
+
element.content = cls.render_dynamic_values(
|
|
416
|
+
content=(element.content or '') + f"{uuid_child} {get_tail}",
|
|
417
|
+
**kwargs
|
|
418
|
+
)
|
|
357
419
|
|
|
358
420
|
if element.tag == 'select' and element.childs:
|
|
359
421
|
try: element.value = __xyza
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from uuid import uuid4
|
|
3
|
-
from pyweber.core.element import Element
|
|
3
|
+
from pyweber.core.element import Element, SEARCH_MODE
|
|
4
4
|
from pyweber.utils.loads import LoadStaticFiles
|
|
5
5
|
from pyweber.config.config import config
|
|
6
6
|
from pyweber.utils.types import HTTPStatusCode, GetBy
|
|
@@ -84,21 +84,43 @@ class Template: # pragma: no cover
|
|
|
84
84
|
|
|
85
85
|
return html
|
|
86
86
|
|
|
87
|
-
def getElement(
|
|
87
|
+
def getElement(
|
|
88
|
+
self,
|
|
89
|
+
by: GetBy,
|
|
90
|
+
value: str,
|
|
91
|
+
element: Element = None,
|
|
92
|
+
search_mode: SEARCH_MODE='exact'
|
|
93
|
+
):
|
|
88
94
|
if not element: element = self.root
|
|
89
|
-
return element.getElement(by=by, value=value)
|
|
90
|
-
|
|
91
|
-
def getElements(
|
|
95
|
+
return element.getElement(by=by, value=value, search_mode=search_mode)
|
|
96
|
+
|
|
97
|
+
def getElements(
|
|
98
|
+
self,
|
|
99
|
+
by: GetBy,
|
|
100
|
+
value: str,
|
|
101
|
+
element: Element = None,
|
|
102
|
+
search_mode: SEARCH_MODE='exact'
|
|
103
|
+
):
|
|
92
104
|
if not element: element = self.root
|
|
93
|
-
return element.getElements(by=by, value=value)
|
|
94
|
-
|
|
95
|
-
def querySelector(
|
|
105
|
+
return element.getElements(by=by, value=value, search_mode=search_mode)
|
|
106
|
+
|
|
107
|
+
def querySelector(
|
|
108
|
+
self,
|
|
109
|
+
selector: str,
|
|
110
|
+
element: Element = None,
|
|
111
|
+
search_mode: SEARCH_MODE='exact'
|
|
112
|
+
):
|
|
96
113
|
if element is None: element = self.__root
|
|
97
|
-
return element.querySelector(selector=selector)
|
|
98
|
-
|
|
99
|
-
def querySelectorAll(
|
|
114
|
+
return element.querySelector(selector=selector, search_mode=search_mode)
|
|
115
|
+
|
|
116
|
+
def querySelectorAll(
|
|
117
|
+
self,
|
|
118
|
+
selector: str,
|
|
119
|
+
element: Element = None,
|
|
120
|
+
search_mode: SEARCH_MODE='exact'
|
|
121
|
+
) -> list[Element]:
|
|
100
122
|
if element is None: element = self.__root
|
|
101
|
-
return element.querySelectorAll(selector=selector)
|
|
123
|
+
return element.querySelectorAll(selector=selector, search_mode=search_mode)
|
|
102
124
|
|
|
103
125
|
def __parse_html(self, html: str) -> Element:
|
|
104
126
|
if not html.replace('<!DOCTYPE html>', '').strip().startswith('<html'):
|
|
@@ -2,12 +2,12 @@ from datetime import datetime, timezone, timedelta
|
|
|
2
2
|
|
|
3
3
|
class CookieManager:
|
|
4
4
|
def __init__(self):
|
|
5
|
-
self.__cookies:
|
|
6
|
-
|
|
5
|
+
self.__cookies: dict[str, str] = {}
|
|
6
|
+
|
|
7
7
|
@property
|
|
8
8
|
def cookies(self):
|
|
9
9
|
return self.__cookies
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
def set_cookie(
|
|
12
12
|
self,
|
|
13
13
|
cookie_name: str,
|
|
@@ -25,10 +25,10 @@ class CookieManager:
|
|
|
25
25
|
|
|
26
26
|
if httponly:
|
|
27
27
|
cookie += ' HttpOnly;'
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
if secure:
|
|
30
30
|
cookie += ' Secure;'
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
if samesite:
|
|
33
33
|
if samesite not in ['Strict', 'Lax']:
|
|
34
34
|
raise ValueError("SameSite is not valid. Please use one of: ['Strict', 'Lax']")
|
|
@@ -54,9 +54,9 @@ class CookieManager:
|
|
|
54
54
|
|
|
55
55
|
expires_str = expires.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
|
56
56
|
cookie += f' Expires={expires_str};'
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
if isinstance(max_age, (int, float)) and max_age > 0:
|
|
59
59
|
cookie += f' Max-Age={max_age};'
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
if cookie not in self.__cookies:
|
|
62
|
-
self.__cookies
|
|
62
|
+
self.__cookies[cookie_name] = cookie
|
|
@@ -307,6 +307,17 @@ class ElementConstrutor: # pragma: no cover
|
|
|
307
307
|
def content(self):
|
|
308
308
|
return self.__content
|
|
309
309
|
|
|
310
|
+
@property
|
|
311
|
+
def text_content(self):
|
|
312
|
+
uuids = re.findall(pattern=r'\{\{(.*?)\}\}', string=self.content or '')
|
|
313
|
+
child_uuids = {child.uuid for child in self.childs if child.uuid in uuids}
|
|
314
|
+
result = self.content or ''
|
|
315
|
+
|
|
316
|
+
for uuid in child_uuids:
|
|
317
|
+
u_ = "{{" + uuid + "}}"
|
|
318
|
+
result = result.replace(u_, '')
|
|
319
|
+
return result.strip()
|
|
320
|
+
|
|
310
321
|
@content.setter
|
|
311
322
|
def content(self, value: str):
|
|
312
323
|
if value is None:
|
|
@@ -471,7 +482,6 @@ class ElementConstrutor: # pragma: no cover
|
|
|
471
482
|
value = self.to_html(element=value)
|
|
472
483
|
|
|
473
484
|
content = content.replace("{{" + r + "}}", str(value))
|
|
474
|
-
|
|
475
485
|
return content
|
|
476
486
|
|
|
477
487
|
def create_event_id(self, event: Union[Callable, str], type: str, element_id: str = None):
|
|
@@ -10,7 +10,7 @@ class Response:
|
|
|
10
10
|
request: Request,
|
|
11
11
|
response_content: bytes,
|
|
12
12
|
code: int,
|
|
13
|
-
cookies:
|
|
13
|
+
cookies: dict[str, str],
|
|
14
14
|
response_type: ContentTypes,
|
|
15
15
|
route: str
|
|
16
16
|
):
|
|
@@ -153,7 +153,7 @@ class Response:
|
|
|
153
153
|
for key, value in self.headers.items():
|
|
154
154
|
if key == 'Set-Cookie':
|
|
155
155
|
for cookie in value:
|
|
156
|
-
response += f'{key}: {cookie}\r\n'
|
|
156
|
+
response += f'{key}: {value[cookie]}\r\n'
|
|
157
157
|
|
|
158
158
|
elif key == 'Response':
|
|
159
159
|
pass
|
|
@@ -109,7 +109,12 @@ class Route: # pragma: no cover
|
|
|
109
109
|
self.__callback = callback or self.template if callable(self.template) else lambda **kwargs: self.template
|
|
110
110
|
|
|
111
111
|
@property
|
|
112
|
-
def full_route(self):
|
|
112
|
+
def full_route(self):
|
|
113
|
+
return f"/{self.group.removeprefix('__')}{self.route}" if self.group != self.default_group() else self.route
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def full_route_with_params(self):
|
|
117
|
+
return f"{self.full_route}{('?' + self.route_with_params.split('?',1)[-1] if self.query_params else '')}"
|
|
113
118
|
|
|
114
119
|
@property
|
|
115
120
|
def middlewares(self): return self.__middlewares
|
|
@@ -330,6 +335,7 @@ class Route: # pragma: no cover
|
|
|
330
335
|
f'group={self.group}, '
|
|
331
336
|
f'route={self.route}, '
|
|
332
337
|
f'full_route={self.full_route}, '
|
|
338
|
+
f'route_with_params={self.route_with_params}, '
|
|
333
339
|
f'name={self.name}, '
|
|
334
340
|
f'methods={self.methods}, '
|
|
335
341
|
f'status_code={self.status_code})'
|
|
@@ -538,10 +544,12 @@ class RouteManager: # pragma: no cover
|
|
|
538
544
|
|
|
539
545
|
def get_route_by_path(self, route: str, follow_redirect: bool = True):
|
|
540
546
|
path, _ = self.resolve_path(route=route)
|
|
547
|
+
path = path.split('?', 1)[0]
|
|
541
548
|
|
|
542
549
|
if follow_redirect in [True, 1] and self.is_redirected(route=path):
|
|
543
550
|
redirect_route = self.get_redirected_route(route=path)
|
|
544
551
|
return redirect_route.route
|
|
552
|
+
|
|
545
553
|
return self.__routes.get(path)
|
|
546
554
|
|
|
547
555
|
def get_route_by_name(self, name: str):
|
|
@@ -477,8 +477,9 @@ class Pyweber(
|
|
|
477
477
|
template = state_result.template
|
|
478
478
|
|
|
479
479
|
while callable(template) or isinstance(template, RedirectRoute):
|
|
480
|
+
request_params = {**self.request.body, **self.request.query_params, 'request': self.request} if self.request else {}
|
|
481
|
+
|
|
480
482
|
if callable(template):
|
|
481
|
-
request_params = {**self.request.body, **self.request.query_params, 'request': self.request} if self.request else {}
|
|
482
483
|
kwargs = OpenApiProcessor.prepare_callback_kwargs(
|
|
483
484
|
callback=state_result.callback,
|
|
484
485
|
**{**state_result.kwargs, **request_params}
|
|
@@ -487,14 +488,15 @@ class Pyweber(
|
|
|
487
488
|
template = await template(**kwargs) if inspect.iscoroutinefunction(template) else template(**kwargs)
|
|
488
489
|
|
|
489
490
|
if isinstance(template, RedirectRoute):
|
|
490
|
-
|
|
491
|
+
kwargs = {**state_result.kwargs, **request_params, **template.kwargs}
|
|
492
|
+
redirect_path = self.build_route(route=template.route.full_route_with_params, **kwargs)
|
|
491
493
|
|
|
492
494
|
self._check_recursion(route=redirect_path)
|
|
493
495
|
state_result = await self._process_redirect_route(
|
|
494
496
|
state=state_result,
|
|
495
497
|
redirect_route=template,
|
|
496
498
|
redirect_path=redirect_path,
|
|
497
|
-
**
|
|
499
|
+
**kwargs
|
|
498
500
|
)
|
|
499
501
|
|
|
500
502
|
template = state_result.template
|
|
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.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/apple-touch-icon.png
RENAMED
|
File without changes
|
{pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/favicon-16x16.png
RENAMED
|
File without changes
|
{pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/favicon-32x32.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/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.dev20260425 → pyweber-1.2.0.dev20260426}/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
|