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.
Files changed (88) hide show
  1. {pyweber-1.2.0.dev20260423/pyweber.egg-info → pyweber-1.2.0.dev20260425}/PKG-INFO +1 -1
  2. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyproject.toml +1 -1
  3. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/core/element.py +130 -49
  4. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/core/template.py +45 -130
  5. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/element.py +21 -16
  6. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425/pyweber.egg-info}/PKG-INFO +1 -1
  7. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/LICENSE +0 -0
  8. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/MANIFEST.in +0 -0
  9. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/README.md +0 -0
  10. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/__init__.py +0 -0
  11. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/admin/index.html +0 -0
  12. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/admin/src/script.js +0 -0
  13. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/admin/src/style.css +0 -0
  14. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/cli/__init__.py +0 -0
  15. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/cli/commands.py +0 -0
  16. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/components/__init__.py +0 -0
  17. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/components/form.py +0 -0
  18. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/components/general.py +0 -0
  19. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/components/input.py +0 -0
  20. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/config/__init__.py +0 -0
  21. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/config/config.py +0 -0
  22. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/connection/__init__.py +0 -0
  23. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/connection/http.py +0 -0
  24. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/connection/reload.py +0 -0
  25. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/connection/selector.py +0 -0
  26. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/connection/session.py +0 -0
  27. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/connection/websocket.py +0 -0
  28. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/core/__init__.py +0 -0
  29. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/core/events.py +0 -0
  30. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/core/window.py +0 -0
  31. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/__init__.py +0 -0
  32. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/cookies.py +0 -0
  33. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/create_app.py +0 -0
  34. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/error_pages.py +0 -0
  35. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/field.py +0 -0
  36. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/field_storage.py +0 -0
  37. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/file.py +0 -0
  38. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/file_stream.py +0 -0
  39. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/headers.py +0 -0
  40. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/middleware.py +0 -0
  41. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/openapi.py +0 -0
  42. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/request.py +0 -0
  43. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/response.py +0 -0
  44. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/routes.py +0 -0
  45. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/run.py +0 -0
  46. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/strem_stats.py +0 -0
  47. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/task_manager.py +0 -0
  48. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/template_diff.py +0 -0
  49. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/models/ws_message.py +0 -0
  50. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/pyweber/__init__.py +0 -0
  51. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/pyweber/pyweber.py +0 -0
  52. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/.gitignore +0 -0
  53. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/config.toml +0 -0
  54. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/css.css +0 -0
  55. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/docs.html +0 -0
  56. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/android-chrome-192x192.png +0 -0
  57. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/android-chrome-512x512.png +0 -0
  58. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/apple-touch-icon.png +0 -0
  59. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/favicon-16x16.png +0 -0
  60. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/favicon-32x32.png +0 -0
  61. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/favicon.ico +0 -0
  62. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/pyweber.png +0 -0
  63. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/favicon/site.webmanifest +0 -0
  64. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/handlers.js +0 -0
  65. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/html.html +0 -0
  66. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/html401.html +0 -0
  67. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/html404.html +0 -0
  68. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/html500.html +0 -0
  69. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/js.js +0 -0
  70. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/loading.html +0 -0
  71. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/main.py +0 -0
  72. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/pyweber.css +0 -0
  73. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/static/update.py +0 -0
  74. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/utils/__init__.py +0 -0
  75. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/utils/exceptions.py +0 -0
  76. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/utils/loads.py +0 -0
  77. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/utils/types.py +0 -0
  78. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber/utils/utils.py +0 -0
  79. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/SOURCES.txt +0 -0
  80. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/dependency_links.txt +0 -0
  81. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/entry_points.txt +0 -0
  82. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/requires.txt +0 -0
  83. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/pyweber.egg-info/top_level.txt +0 -0
  84. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/setup.cfg +0 -0
  85. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/tests/test_config.py +0 -0
  86. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/tests/test_cookies.py +0 -0
  87. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/tests/test_response.py +0 -0
  88. {pyweber-1.2.0.dev20260423 → pyweber-1.2.0.dev20260425}/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.dev20260423
3
+ Version: 1.2.0.dev20260425
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.dev20260423"
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(include_uuid=self.__include_uuid)
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
- root: HTMLPARSER.HtmlElement = HTMLPARSER.fromstring(html=html)
115
-
116
- if root.find(path='head') is None:
117
- root.insert(0, LXML_Element('head'))
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
- if self.tag == 'select':
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: 'ElementConstrutor' = None, indent: int = 0, include_uuid: bool = True):
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.__render_dynamic_values(content=element.content, include_uuid=include_uuid) or ''))
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, include_uuid=include_uuid)
436
- uuid_placeholder = f'{{{child.uuid}}}'
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
- def __render_dynamic_values(self, content: str, include_uuid: bool = True):
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 = self.kwargs.get(r.strip(), None)
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, include_uuid=include_uuid)
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyweber
3
- Version: 1.2.0.dev20260423
3
+ Version: 1.2.0.dev20260425
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