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