pyweber 1.2.0.dev20260424__tar.gz → 1.2.0.dev20260425__tar.gz

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