jx 0.1.0__tar.gz → 0.3.0__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.
- jx-0.3.0/PKG-INFO +75 -0
- jx-0.3.0/README.md +49 -0
- {jx-0.1.0 → jx-0.3.0}/pyproject.toml +12 -13
- {jx-0.1.0 → jx-0.3.0}/src/jx/__init__.py +2 -1
- {jx-0.1.0 → jx-0.3.0}/src/jx/attrs.py +93 -48
- jx-0.3.0/src/jx/catalog.py +381 -0
- {jx-0.1.0 → jx-0.3.0}/src/jx/component.py +74 -33
- {jx-0.1.0 → jx-0.3.0}/src/jx/exceptions.py +3 -1
- {jx-0.1.0 → jx-0.3.0}/src/jx/meta.py +7 -5
- jx-0.3.0/src/jx/parser.py +411 -0
- jx-0.3.0/src/jx/utils.py +26 -0
- jx-0.3.0/src/jx.egg-info/PKG-INFO +75 -0
- {jx-0.1.0 → jx-0.3.0}/src/jx.egg-info/SOURCES.txt +1 -0
- jx-0.3.0/src/jx.egg-info/requires.txt +1 -0
- {jx-0.1.0 → jx-0.3.0}/tests/test_attrs.py +23 -18
- {jx-0.1.0 → jx-0.3.0}/tests/test_catalog.py +23 -1
- {jx-0.1.0 → jx-0.3.0}/tests/test_component.py +41 -1
- {jx-0.1.0 → jx-0.3.0}/tests/test_meta.py +2 -1
- jx-0.3.0/tests/test_parser.py +313 -0
- jx-0.3.0/tests/test_slots.py +206 -0
- {jx-0.1.0 → jx-0.3.0}/tests/test_thread_safety.py +2 -1
- jx-0.1.0/PKG-INFO +0 -42
- jx-0.1.0/README.md +0 -14
- jx-0.1.0/src/jx/catalog.py +0 -227
- jx-0.1.0/src/jx/parser.py +0 -215
- jx-0.1.0/src/jx/utils.py +0 -13
- jx-0.1.0/src/jx.egg-info/PKG-INFO +0 -42
- jx-0.1.0/src/jx.egg-info/requires.txt +0 -2
- jx-0.1.0/tests/test_parser.py +0 -171
- {jx-0.1.0 → jx-0.3.0}/LICENSE +0 -0
- {jx-0.1.0 → jx-0.3.0}/setup.cfg +0 -0
- {jx-0.1.0 → jx-0.3.0}/src/jx.egg-info/dependency_links.txt +0 -0
- {jx-0.1.0 → jx-0.3.0}/src/jx.egg-info/top_level.txt +0 -0
jx-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jx
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Replace your HTML templates with Python server-Side components
|
|
5
|
+
Author-email: Juan Pablo Scaletti <juanpablo@jpscaletti.com>
|
|
6
|
+
Project-URL: Homepage, https://jx.scaletti.dev/
|
|
7
|
+
Project-URL: GitHub, https://github.com/jpsca/jx
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Web Environment
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
19
|
+
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: <4,>=3.12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: jinja2>=3.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
<h1>
|
|
28
|
+
<img src="./docs/logo-jx.png" height="100" align="top">
|
|
29
|
+
</h1>
|
|
30
|
+
|
|
31
|
+
Super components powers for your Jinja templates.
|
|
32
|
+
|
|
33
|
+
<p>
|
|
34
|
+
<img alt="python: 3.11, 3.12, 3.13, 3.14" src="./docs/python.svg">
|
|
35
|
+
<img alt="license: MIT" src="./docs/license.svg">
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
From chaos to clarity: The power of components in your server-side-rendered Python web app.
|
|
39
|
+
|
|
40
|
+
<!-- Documentation: https://jx.scaletti.dev/ -->
|
|
41
|
+
|
|
42
|
+
## How It Works
|
|
43
|
+
|
|
44
|
+
Jx is a Python library for creating reusable template components with Jinja2. It works by pre-parsing the template source and replacing TitleCased HTML tags with Jinja calls that render the component.
|
|
45
|
+
|
|
46
|
+
### Component Definition
|
|
47
|
+
|
|
48
|
+
Components are defined as regular Jinja2 templates (.jinja files) with special metadata comments:
|
|
49
|
+
|
|
50
|
+
- `{# def parameter1 parameter2=default_value #}` - Defines required and optional parameters
|
|
51
|
+
- `{# import "path/to/component.jinja" as ComponentName #}` - Imports other components
|
|
52
|
+
- `{# css "/path/to/style.css" #}` - Includes CSS files
|
|
53
|
+
- `{# js "/path/to/script.js" #}` - Includes JavaScript files
|
|
54
|
+
|
|
55
|
+
Example component:
|
|
56
|
+
|
|
57
|
+
```jinja
|
|
58
|
+
{# def message #}
|
|
59
|
+
{# import "button.jinja" as Button #}
|
|
60
|
+
|
|
61
|
+
<div class="greeting">{{ message }}</div>
|
|
62
|
+
<Button text="OK" />
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Usage Example
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from jx import Catalog
|
|
69
|
+
|
|
70
|
+
# Create a catalog and add a components folder
|
|
71
|
+
catalog = Catalog("templates/components")
|
|
72
|
+
|
|
73
|
+
# Render a component with parameters
|
|
74
|
+
html = catalog.render("card.jinja", title="Hello", content="This is a card")
|
|
75
|
+
```
|
jx-0.3.0/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<h1>
|
|
2
|
+
<img src="./docs/logo-jx.png" height="100" align="top">
|
|
3
|
+
</h1>
|
|
4
|
+
|
|
5
|
+
Super components powers for your Jinja templates.
|
|
6
|
+
|
|
7
|
+
<p>
|
|
8
|
+
<img alt="python: 3.11, 3.12, 3.13, 3.14" src="./docs/python.svg">
|
|
9
|
+
<img alt="license: MIT" src="./docs/license.svg">
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
From chaos to clarity: The power of components in your server-side-rendered Python web app.
|
|
13
|
+
|
|
14
|
+
<!-- Documentation: https://jx.scaletti.dev/ -->
|
|
15
|
+
|
|
16
|
+
## How It Works
|
|
17
|
+
|
|
18
|
+
Jx is a Python library for creating reusable template components with Jinja2. It works by pre-parsing the template source and replacing TitleCased HTML tags with Jinja calls that render the component.
|
|
19
|
+
|
|
20
|
+
### Component Definition
|
|
21
|
+
|
|
22
|
+
Components are defined as regular Jinja2 templates (.jinja files) with special metadata comments:
|
|
23
|
+
|
|
24
|
+
- `{# def parameter1 parameter2=default_value #}` - Defines required and optional parameters
|
|
25
|
+
- `{# import "path/to/component.jinja" as ComponentName #}` - Imports other components
|
|
26
|
+
- `{# css "/path/to/style.css" #}` - Includes CSS files
|
|
27
|
+
- `{# js "/path/to/script.js" #}` - Includes JavaScript files
|
|
28
|
+
|
|
29
|
+
Example component:
|
|
30
|
+
|
|
31
|
+
```jinja
|
|
32
|
+
{# def message #}
|
|
33
|
+
{# import "button.jinja" as Button #}
|
|
34
|
+
|
|
35
|
+
<div class="greeting">{{ message }}</div>
|
|
36
|
+
<Button text="OK" />
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Usage Example
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from jx import Catalog
|
|
43
|
+
|
|
44
|
+
# Create a catalog and add a components folder
|
|
45
|
+
catalog = Catalog("templates/components")
|
|
46
|
+
|
|
47
|
+
# Render a component with parameters
|
|
48
|
+
html = catalog.render("card.jinja", title="Hello", content="This is a card")
|
|
49
|
+
```
|
|
@@ -4,7 +4,7 @@ requires = ["setuptools"]
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "jx"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Replace your HTML templates with Python server-Side components"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Juan Pablo Scaletti", email = "juanpablo@jpscaletti.com"},
|
|
@@ -26,24 +26,24 @@ classifiers = [
|
|
|
26
26
|
"Topic :: Text Processing :: Markup :: HTML",
|
|
27
27
|
"Typing :: Typed",
|
|
28
28
|
]
|
|
29
|
-
requires-python = ">=3.
|
|
29
|
+
requires-python = ">=3.12,<4"
|
|
30
30
|
dependencies = [
|
|
31
31
|
"jinja2 >= 3.0",
|
|
32
|
-
"markupsafe >= 2.0",
|
|
33
32
|
]
|
|
34
33
|
|
|
35
34
|
[project.urls]
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
documentation = "https://jx.scaletti.dev/"
|
|
35
|
+
Homepage = "https://jx.scaletti.dev/"
|
|
36
|
+
GitHub = "https://github.com/jpsca/jx"
|
|
39
37
|
|
|
40
38
|
[dependency-groups]
|
|
41
39
|
dev = [
|
|
42
|
-
"ipdb
|
|
40
|
+
"ipdb",
|
|
43
41
|
"pre-commit",
|
|
44
|
-
"ruff >= 0.2.0",
|
|
45
42
|
"tox-uv",
|
|
46
|
-
"ty
|
|
43
|
+
"ty",
|
|
44
|
+
]
|
|
45
|
+
docs = [
|
|
46
|
+
"writeadoc>=0.7.0",
|
|
47
47
|
]
|
|
48
48
|
test = [
|
|
49
49
|
"pytest >= 7.2",
|
|
@@ -81,10 +81,9 @@ addopts = "--doctest-modules"
|
|
|
81
81
|
legacy_tox_ini = """
|
|
82
82
|
[tox]
|
|
83
83
|
env_list =
|
|
84
|
-
3.11
|
|
85
|
-
3.12
|
|
86
|
-
3.13
|
|
87
84
|
3.14
|
|
85
|
+
3.13
|
|
86
|
+
3.12
|
|
88
87
|
|
|
89
88
|
[testenv]
|
|
90
89
|
runner = uv-venv-lock-runner
|
|
@@ -99,7 +98,7 @@ commands =
|
|
|
99
98
|
[tool.ruff]
|
|
100
99
|
line-length = 90
|
|
101
100
|
indent-width = 4
|
|
102
|
-
target-version = "
|
|
101
|
+
target-version = "py312"
|
|
103
102
|
exclude = [
|
|
104
103
|
".*",
|
|
105
104
|
"_build",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Jx | Copyright (c) Juan-Pablo Scaletti
|
|
2
|
+
Jx | Copyright (c) Juan-Pablo Scaletti
|
|
3
3
|
"""
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
import typing as t
|
|
6
6
|
from collections import UserString
|
|
7
7
|
from functools import cached_property
|
|
@@ -14,10 +14,6 @@ CLASS_ALT_KEY = "classes"
|
|
|
14
14
|
CLASS_KEYS = (CLASS_KEY, CLASS_ALT_KEY)
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def split(ssl: str) -> list[str]:
|
|
18
|
-
return re.split(r"\s+", ssl.strip())
|
|
19
|
-
|
|
20
|
-
|
|
21
17
|
def quote(text: str) -> str:
|
|
22
18
|
if '"' in text:
|
|
23
19
|
if "'" in text:
|
|
@@ -30,14 +26,13 @@ def quote(text: str) -> str:
|
|
|
30
26
|
|
|
31
27
|
|
|
32
28
|
class LazyString(UserString):
|
|
33
|
-
"""
|
|
34
|
-
Behave like regular strings, but the actual casting of the initial value
|
|
35
|
-
is deferred until the value is actually required.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
29
|
__slots__ = ("_seq",)
|
|
39
30
|
|
|
40
31
|
def __init__(self, seq):
|
|
32
|
+
"""
|
|
33
|
+
Behave like regular strings, but the actual casting of the initial value
|
|
34
|
+
is deferred until the value is actually required.
|
|
35
|
+
"""
|
|
41
36
|
self._seq = seq
|
|
42
37
|
|
|
43
38
|
@cached_property
|
|
@@ -46,29 +41,36 @@ class LazyString(UserString):
|
|
|
46
41
|
|
|
47
42
|
|
|
48
43
|
class Attrs:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
in the declared attributes list.
|
|
44
|
+
__classes: tuple[str, ...]
|
|
45
|
+
__attributes: dict[str, str | LazyString]
|
|
46
|
+
__properties: set[str]
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
def __init__(self, attrs: "dict[str, t.Any| LazyString]") -> None:
|
|
49
|
+
"""
|
|
50
|
+
Contains all the HTML attributes/properties (a property is an
|
|
51
|
+
attribute without a value) passed to a component but that weren't
|
|
52
|
+
in the declared attributes list.
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
`attrs.render()` is invoked.
|
|
54
|
+
For HTML classes you can use the name "classes" (instead of "class")
|
|
55
|
+
if you need to.
|
|
60
56
|
|
|
61
|
-
|
|
57
|
+
**NOTE**: The string values passed to this class, are not cast to `str` until
|
|
58
|
+
the string representation is actually needed, for example when
|
|
59
|
+
`attrs.render()` is invoked.
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
"""
|
|
64
62
|
attributes: "dict[str, str | LazyString]" = {}
|
|
65
63
|
properties: set[str] = set()
|
|
66
64
|
|
|
67
|
-
class_names =
|
|
65
|
+
class_names = (" ".join([
|
|
68
66
|
str(attrs.pop(CLASS_KEY, "")),
|
|
69
67
|
str(attrs.get(CLASS_ALT_KEY, "")),
|
|
70
|
-
]))
|
|
71
|
-
|
|
68
|
+
])).strip().split()
|
|
69
|
+
classes = []
|
|
70
|
+
for name in class_names:
|
|
71
|
+
if name and name not in classes:
|
|
72
|
+
classes.append(name)
|
|
73
|
+
self.__classes = tuple(classes)
|
|
72
74
|
|
|
73
75
|
for name, value in attrs.items():
|
|
74
76
|
if name.startswith("_"):
|
|
@@ -85,19 +87,19 @@ class Attrs:
|
|
|
85
87
|
@property
|
|
86
88
|
def classes(self) -> str:
|
|
87
89
|
"""
|
|
88
|
-
All the HTML classes
|
|
90
|
+
All the HTML classes separated by a space.
|
|
89
91
|
|
|
90
92
|
Example:
|
|
91
93
|
|
|
92
94
|
```python
|
|
93
|
-
attrs =
|
|
95
|
+
attrs = Attrs({"class": "italic bold bg-blue wide abcde"})
|
|
94
96
|
attrs.set(class="bold text-white")
|
|
95
97
|
print(attrs.classes)
|
|
96
|
-
|
|
98
|
+
italic bold bg-blue wide abcde text-white
|
|
97
99
|
```
|
|
98
100
|
|
|
99
101
|
"""
|
|
100
|
-
return " ".join(
|
|
102
|
+
return " ".join(self.__classes)
|
|
101
103
|
|
|
102
104
|
@property
|
|
103
105
|
def as_dict(self) -> dict[str, t.Any]:
|
|
@@ -108,7 +110,7 @@ class Attrs:
|
|
|
108
110
|
Example:
|
|
109
111
|
|
|
110
112
|
```python
|
|
111
|
-
attrs =
|
|
113
|
+
attrs = Attrs({
|
|
112
114
|
"class": "lorem ipsum",
|
|
113
115
|
"data_test": True,
|
|
114
116
|
"hidden": True,
|
|
@@ -118,7 +120,7 @@ class Attrs:
|
|
|
118
120
|
attrs.as_dict
|
|
119
121
|
{
|
|
120
122
|
"aria_label": "hello",
|
|
121
|
-
"class": "ipsum
|
|
123
|
+
"class": "lorem ipsum",
|
|
122
124
|
"id": "world",
|
|
123
125
|
"data_test": True,
|
|
124
126
|
"hidden": True
|
|
@@ -160,7 +162,7 @@ class Attrs:
|
|
|
160
162
|
Example:
|
|
161
163
|
|
|
162
164
|
```python
|
|
163
|
-
attrs =
|
|
165
|
+
attrs = Attrs({"secret": "qwertyuiop"})
|
|
164
166
|
attrs.set(secret=False)
|
|
165
167
|
attrs.as_dict
|
|
166
168
|
{}
|
|
@@ -169,10 +171,10 @@ class Attrs:
|
|
|
169
171
|
attrs.as_dict
|
|
170
172
|
{"count":42, "lorem":"ipsum", "data_good": True}
|
|
171
173
|
|
|
172
|
-
attrs =
|
|
174
|
+
attrs = Attrs({"class": "b c a"})
|
|
173
175
|
attrs.set(class="c b f d e")
|
|
174
176
|
attrs.as_dict
|
|
175
|
-
{"class": "
|
|
177
|
+
{"class": "b c a f d e"}
|
|
176
178
|
```
|
|
177
179
|
|
|
178
180
|
"""
|
|
@@ -199,7 +201,7 @@ class Attrs:
|
|
|
199
201
|
Example:
|
|
200
202
|
|
|
201
203
|
```python
|
|
202
|
-
attrs =
|
|
204
|
+
attrs = Attrs({"lorem": "ipsum"})
|
|
203
205
|
attrs.setdefault(tabindex=0, lorem="meh")
|
|
204
206
|
attrs.as_dict
|
|
205
207
|
# "tabindex" changed but "lorem" didn't
|
|
@@ -212,7 +214,9 @@ class Attrs:
|
|
|
212
214
|
continue
|
|
213
215
|
|
|
214
216
|
if name in CLASS_KEYS:
|
|
215
|
-
self.
|
|
217
|
+
if not self.__classes:
|
|
218
|
+
self.add_class(value)
|
|
219
|
+
continue
|
|
216
220
|
|
|
217
221
|
name = name.replace("_", "-")
|
|
218
222
|
if name not in self.__attributes:
|
|
@@ -220,21 +224,55 @@ class Attrs:
|
|
|
220
224
|
|
|
221
225
|
def add_class(self, *values: str) -> None:
|
|
222
226
|
"""
|
|
223
|
-
Adds one or more classes to the list of classes,
|
|
227
|
+
Adds one or more classes to the end of the list of classes,
|
|
228
|
+
if not already present.
|
|
229
|
+
|
|
230
|
+
Arguments:
|
|
231
|
+
values:
|
|
232
|
+
One or more class names to add, separated by spaces.
|
|
224
233
|
|
|
225
234
|
Example:
|
|
226
235
|
|
|
227
236
|
```python
|
|
228
|
-
attrs =
|
|
229
|
-
attrs.add_class("c
|
|
237
|
+
attrs = Attrs({"class": "a b c"})
|
|
238
|
+
attrs.add_class("c d")
|
|
230
239
|
attrs.as_dict
|
|
231
240
|
{"class": "a b c d"}
|
|
232
241
|
```
|
|
233
242
|
|
|
234
243
|
"""
|
|
235
244
|
for names in values:
|
|
236
|
-
for name in split(
|
|
237
|
-
self.__classes
|
|
245
|
+
for name in names.strip().split():
|
|
246
|
+
if name not in self.__classes:
|
|
247
|
+
self.__classes += (name,)
|
|
248
|
+
|
|
249
|
+
def prepend_class(self, *values: str) -> None:
|
|
250
|
+
"""
|
|
251
|
+
Adds one or more classes to the beginning of the list of classes,
|
|
252
|
+
if not already present.
|
|
253
|
+
|
|
254
|
+
Arguments:
|
|
255
|
+
values:
|
|
256
|
+
One or more class names to add, separated by spaces.
|
|
257
|
+
|
|
258
|
+
Example:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
attrs = Attrs({"class": "a b c"})
|
|
262
|
+
attrs.add_class("c d |")
|
|
263
|
+
attrs.as_dict
|
|
264
|
+
{"class": "d | a b c"}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
"""
|
|
268
|
+
new_classes = [
|
|
269
|
+
name
|
|
270
|
+
for names in values
|
|
271
|
+
for name in names.strip().split()
|
|
272
|
+
if name not in self.__classes
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
self.__classes = tuple(new_classes) + self.__classes
|
|
238
276
|
|
|
239
277
|
def remove_class(self, *names: str) -> None:
|
|
240
278
|
"""
|
|
@@ -243,25 +281,32 @@ class Attrs:
|
|
|
243
281
|
Example:
|
|
244
282
|
|
|
245
283
|
```python
|
|
246
|
-
attrs =
|
|
284
|
+
attrs = Attrs({"class": "a b c"})
|
|
247
285
|
attrs.remove_class("c", "d")
|
|
248
286
|
attrs.as_dict
|
|
249
287
|
{"class": "a b"}
|
|
250
288
|
```
|
|
251
289
|
|
|
252
290
|
"""
|
|
253
|
-
for
|
|
254
|
-
self.__classes.remove(name)
|
|
291
|
+
self.__classes = tuple(c for c in self.__classes if c not in names)
|
|
255
292
|
|
|
256
293
|
def get(self, name: str, default: t.Any = None) -> t.Any:
|
|
257
294
|
"""
|
|
258
295
|
Returns the value of the attribute or property,
|
|
259
296
|
or the default value if it doesn't exists.
|
|
260
297
|
|
|
298
|
+
Arguments:
|
|
299
|
+
|
|
300
|
+
name:
|
|
301
|
+
The name of the attribute or property to get.
|
|
302
|
+
|
|
303
|
+
default:
|
|
304
|
+
The default value to return if the attribute or property doesn't exist.
|
|
305
|
+
|
|
261
306
|
Example:
|
|
262
307
|
|
|
263
308
|
```python
|
|
264
|
-
attrs =
|
|
309
|
+
attrs = Attrs({"lorem": "ipsum", "hidden": True})
|
|
265
310
|
|
|
266
311
|
attrs.get("lorem", defaut="bar")
|
|
267
312
|
'ipsum'
|
|
@@ -291,7 +336,7 @@ class Attrs:
|
|
|
291
336
|
Renders the attributes and properties as a string.
|
|
292
337
|
|
|
293
338
|
Any arguments you use with this function are merged with the existing
|
|
294
|
-
attibutes/properties by the same rules as the `
|
|
339
|
+
attibutes/properties by the same rules as the `Attrs.set()` function:
|
|
295
340
|
|
|
296
341
|
- Pass a name and a value to set an attribute (e.g. `type="text"`)
|
|
297
342
|
- Use `True` as a value to set a property (e.g. `disabled`)
|
|
@@ -308,7 +353,7 @@ class Attrs:
|
|
|
308
353
|
Example:
|
|
309
354
|
|
|
310
355
|
```python
|
|
311
|
-
attrs =
|
|
356
|
+
attrs = Attrs({"class": "ipsum", "data_good": True, "width": 42})
|
|
312
357
|
|
|
313
358
|
attrs.render()
|
|
314
359
|
'class="ipsum" width="42" data-good'
|
|
@@ -345,7 +390,7 @@ class Attrs:
|
|
|
345
390
|
Removes an attribute or property.
|
|
346
391
|
"""
|
|
347
392
|
if name in CLASS_KEYS:
|
|
348
|
-
self.__classes =
|
|
393
|
+
self.__classes = ()
|
|
349
394
|
if name in self.__attributes:
|
|
350
395
|
del self.__attributes[name]
|
|
351
396
|
if name in self.__properties:
|