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.
Files changed (88) hide show
  1. {pyweber-1.2.0.dev20260425/pyweber.egg-info → pyweber-1.2.0.dev20260426}/PKG-INFO +1 -1
  2. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyproject.toml +1 -1
  3. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/core/element.py +93 -31
  4. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/core/template.py +34 -12
  5. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/cookies.py +8 -8
  6. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/element.py +11 -1
  7. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/response.py +2 -2
  8. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/routes.py +9 -1
  9. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/pyweber/pyweber.py +5 -3
  10. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426/pyweber.egg-info}/PKG-INFO +1 -1
  11. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/LICENSE +0 -0
  12. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/MANIFEST.in +0 -0
  13. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/README.md +0 -0
  14. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/__init__.py +0 -0
  15. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/admin/index.html +0 -0
  16. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/admin/src/script.js +0 -0
  17. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/admin/src/style.css +0 -0
  18. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/cli/__init__.py +0 -0
  19. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/cli/commands.py +0 -0
  20. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/components/__init__.py +0 -0
  21. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/components/form.py +0 -0
  22. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/components/general.py +0 -0
  23. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/components/input.py +0 -0
  24. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/config/__init__.py +0 -0
  25. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/config/config.py +0 -0
  26. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/connection/__init__.py +0 -0
  27. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/connection/http.py +0 -0
  28. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/connection/reload.py +0 -0
  29. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/connection/selector.py +0 -0
  30. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/connection/session.py +0 -0
  31. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/connection/websocket.py +0 -0
  32. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/core/__init__.py +0 -0
  33. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/core/events.py +0 -0
  34. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/core/window.py +0 -0
  35. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/__init__.py +0 -0
  36. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/create_app.py +0 -0
  37. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/error_pages.py +0 -0
  38. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/field.py +0 -0
  39. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/field_storage.py +0 -0
  40. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/file.py +0 -0
  41. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/file_stream.py +0 -0
  42. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/headers.py +0 -0
  43. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/middleware.py +0 -0
  44. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/openapi.py +0 -0
  45. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/request.py +0 -0
  46. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/run.py +0 -0
  47. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/strem_stats.py +0 -0
  48. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/task_manager.py +0 -0
  49. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/template_diff.py +0 -0
  50. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/models/ws_message.py +0 -0
  51. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/pyweber/__init__.py +0 -0
  52. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/.gitignore +0 -0
  53. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/config.toml +0 -0
  54. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/css.css +0 -0
  55. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/docs.html +0 -0
  56. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/android-chrome-192x192.png +0 -0
  57. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/android-chrome-512x512.png +0 -0
  58. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/apple-touch-icon.png +0 -0
  59. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/favicon-16x16.png +0 -0
  60. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/favicon-32x32.png +0 -0
  61. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/favicon.ico +0 -0
  62. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/pyweber.png +0 -0
  63. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/favicon/site.webmanifest +0 -0
  64. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/handlers.js +0 -0
  65. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/html.html +0 -0
  66. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/html401.html +0 -0
  67. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/html404.html +0 -0
  68. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/html500.html +0 -0
  69. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/js.js +0 -0
  70. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/loading.html +0 -0
  71. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/main.py +0 -0
  72. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/pyweber.css +0 -0
  73. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/static/update.py +0 -0
  74. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/utils/__init__.py +0 -0
  75. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/utils/exceptions.py +0 -0
  76. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/utils/loads.py +0 -0
  77. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/utils/types.py +0 -0
  78. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber/utils/utils.py +0 -0
  79. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber.egg-info/SOURCES.txt +0 -0
  80. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber.egg-info/dependency_links.txt +0 -0
  81. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber.egg-info/entry_points.txt +0 -0
  82. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber.egg-info/requires.txt +0 -0
  83. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/pyweber.egg-info/top_level.txt +0 -0
  84. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/setup.cfg +0 -0
  85. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/tests/test_config.py +0 -0
  86. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/tests/test_cookies.py +0 -0
  87. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/tests/test_response.py +0 -0
  88. {pyweber-1.2.0.dev20260425 → pyweber-1.2.0.dev20260426}/tests/test_template_diff.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyweber
3
- Version: 1.2.0.dev20260425
3
+ Version: 1.2.0.dev20260426
4
4
  Summary: A lightweight Python framework for building and managing web applications.
5
5
  Author-email: DevPythonMZ <pypi.dev@gmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyweber"
7
- version = "1.2.0.dev20260425"
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(self, by: GetBy, value: str, element: 'Element' = None) -> list['Element']:
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 set(value.split()) <= set(element.classes):
229
+ if matches(element.classes, value):
160
230
  results.append(element)
161
231
 
162
232
  elif by in ['attrs', 'style']:
163
- conditions: list[str] = [pair.strip() for pair in value.split(';') if pair.strip()]
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) == value:
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=dict(attrib),
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 = (element.content or '') + f"{uuid_child} {get_tail}"
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(self, by: GetBy, value: str, element: Element = None):
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(self, by: GetBy, value: str, element: Element = None):
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(self, selector: str, element: Element = None):
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(self, selector: str, element: Element = None) -> list[Element]:
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: list[str] = []
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.append(cookie)
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: list[str],
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): return f"/{self.group.removeprefix('__')}{self.route}" if self.group != self.default_group() else self.route
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
- redirect_path = self.build_route(route=template.route.full_route, **{**state_result.kwargs, **template.kwargs})
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
- **state_result.kwargs
499
+ **kwargs
498
500
  )
499
501
 
500
502
  template = state_result.template
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyweber
3
- Version: 1.2.0.dev20260425
3
+ Version: 1.2.0.dev20260426
4
4
  Summary: A lightweight Python framework for building and managing web applications.
5
5
  Author-email: DevPythonMZ <pypi.dev@gmail.com>
6
6
  License-Expression: MIT