Skip to content

Commit 6d2431d

Browse files
committed
Make attrs use Typed(dict), add Tag.find_by_id, cleanup tests
1 parent 3bf3671 commit 6d2431d

File tree

9 files changed

+89
-37
lines changed

9 files changed

+89
-37
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# 0.12.3
2+
- Make attrs use Typed(dict) to avoid creating an empty dict for each node
3+
- Make attrs remove old keys when attrs changes
4+
- Add Tag.find_by_id
5+
16
# 0.12.2
27
- Use @observe from enaml to avoid unecessary notifications during init_widget
38
- Improve speedup code
File renamed without changes.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
setup(
1717
name='enaml-web',
18-
version='0.12.2',
18+
version='0.12.3',
1919
author='CodeLV',
2020
author_email='[email protected]',
2121
url='https://github.com/codelv/enaml-web',

tests/utils.py renamed to tests/conftest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import pytest
12
from enaml.core.enaml_compiler import EnamlCompiler
23
from enaml.core.parser import parse
4+
from web.core.app import WebApplication
35

46

57
def compile_source(source, item, filename="<test>", namespace=None):
@@ -25,3 +27,9 @@ def compile_source(source, item, filename="<test>", namespace=None):
2527
namespace = namespace or {}
2628
exec(code, namespace)
2729
return namespace[item]
30+
31+
32+
@pytest.fixture
33+
def app():
34+
app = WebApplication.instance() or WebApplication()
35+
yield app

tests/test_block.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
import pytest
22
from textwrap import dedent
3-
from utils import compile_source
4-
from web.core.app import WebApplication
5-
6-
7-
@pytest.fixture
8-
def app():
9-
app = WebApplication.instance() or WebApplication()
10-
yield app
3+
from conftest import compile_source
114

125

136
def test_block(app):

tests/test_html.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
import pytest
33
from textwrap import dedent
44
from lxml import html
5-
from utils import compile_source
6-
from web.core.app import WebApplication
5+
from conftest import compile_source
76

87
try:
98
import nbformat # noqa: F401
@@ -20,12 +19,6 @@
2019
MARKDOWN_UNAVAILABLE = True
2120

2221

23-
@pytest.fixture
24-
def app():
25-
app = WebApplication.instance() or WebApplication()
26-
yield app
27-
28-
2922
def test_hello_world(app):
3023
Page = compile_source(
3124
dedent(
@@ -136,6 +129,34 @@ def test_html(app, tag, attr, query):
136129
("A", "ondragenter", '"a"', "b", '//a[@ondragenter="b"]'),
137130
("A", "ondragleave", '"a"', "b", '//a[@ondragleave="b"]'),
138131
("A", "ondrop", '"a"', "b", '//a[@ondrop="b"]'),
132+
(
133+
"A",
134+
"attrs",
135+
{"data-x": "a"},
136+
{"data-x": "b"},
137+
'//a[@data-x="b"]',
138+
), # attrs value changed
139+
(
140+
"A",
141+
"attrs",
142+
None,
143+
{"data-x": "b"},
144+
'//a[@data-x="b"]',
145+
), # attrs from none to new
146+
(
147+
"A",
148+
"attrs",
149+
{"data-x": "b"},
150+
None,
151+
"//a[not(@data-x)]",
152+
), # attrs from old to none
153+
( # Test attrs key change
154+
"A",
155+
"attrs",
156+
{"data-x": "a"},
157+
{"data-y": "b"},
158+
'//a[@data-y="b" and not(@data-x)]',
159+
),
139160
),
140161
)
141162
def test_html_change(app, tag, attr, default, change, query):

tests/test_speed.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,6 @@
99
from pages import HelloWorld, Simple, ListView
1010

1111

12-
@pytest.fixture
13-
def app():
14-
from web.core.app import WebApplication
15-
16-
app = WebApplication.instance() or WebApplication()
17-
yield app
18-
19-
2012
@pytest.mark.benchmark(group="hello")
2113
def test_hello_world_jinja(app, benchmark):
2214
with open("{}/templates/hello_world.html".format(TEMPLATE_DIR)) as f:

web/components/html.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from atom.api import (
1717
Event,
1818
Enum,
19-
Dict,
2019
Value,
2120
Str,
2221
Instance,
@@ -102,7 +101,7 @@ class Tag(ToolkitObject):
102101
alt = d_(Str()).tag(attr=False)
103102

104103
#: Custom attributes not explicitly defined
105-
attrs = d_(Dict()).tag(attr=False)
104+
attrs = d_(Typed(dict)).tag(attr=False)
106105

107106
#: JS onclick definition
108107
onclick = d_(Str()).tag(attr=False)
@@ -183,8 +182,9 @@ def _update_proxy(self, change: ChangeDict):
183182
value = change["value"]
184183
proxy = self.proxy
185184
assert proxy is not None
186-
handler = getattr(proxy, f"set_{name}", None)
187-
if handler is not None:
185+
if name == "attrs":
186+
proxy.set_attrs(value, change["oldvalue"])
187+
elif handler := getattr(proxy, f"set_{name}", None):
188188
handler(value)
189189
else:
190190
proxy.set_attribute(name, value)
@@ -281,6 +281,25 @@ def _child_index(self, child: Tag) -> int:
281281
# =========================================================================
282282
# Tag API
283283
# =========================================================================
284+
def find_by_id(self, id: str) -> Optional[Tag]:
285+
"""Find a child node with the given id.
286+
287+
Parameters
288+
----------
289+
id: str
290+
The id to look for.
291+
292+
Returns
293+
-------
294+
results: Optional[Tag]
295+
The first node with the given id or None.
296+
297+
"""
298+
for child in self.traverse():
299+
if isinstance(child, Tag) and child.id == id:
300+
return child
301+
return None
302+
284303
def xpath(self, query: str, **kwargs) -> list[Tag]:
285304
"""Find nodes matching the given xpath query
286305

web/impl/lxml_toolkit_object.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,13 @@ def init_widget(self):
111111
set_attr("ondragleave", v)
112112
if v := d.ondrop:
113113
set_attr("ondrop", v)
114-
115-
for k, v in d.attrs.items():
116-
set_attr(k, v)
114+
if attrs := d.attrs:
115+
for k, v in attrs.items():
116+
set_attr(k, v)
117117

118118
for m in get_fields(d.__class__):
119119
name = m.name
120-
value = m.do_getattr(d)
120+
value = getattr(d, name)
121121
if value is True:
122122
widget.set(name, name)
123123
elif value:
@@ -284,13 +284,27 @@ def set_tag(self, tag: str):
284284
assert w is not None
285285
w.tag = tag
286286

287-
def set_attrs(self, attrs: dict[str, str]):
287+
def set_attrs(
288+
self, attrs: Optional[dict[str, str]], oldattrs: Optional[dict[str, str]]
289+
):
288290
"""Set any attributes not explicitly defined"""
289291
w = self.widget
290292
assert w is not None
291-
set_attr = w.set
292-
for k, v in attrs.items():
293-
set_attr(k, v)
293+
if attrs and oldattrs:
294+
# Attrs changed
295+
for k in oldattrs:
296+
if k not in attrs:
297+
w.attrib.pop(k, None)
298+
for k, v in attrs.items():
299+
w.set(k, v)
300+
elif not attrs and oldattrs:
301+
# All attrs removed
302+
for k in oldattrs:
303+
w.attrib.pop(k, None)
304+
elif attrs and not oldattrs:
305+
# Only new attrs
306+
for k, v in attrs.items():
307+
w.set(k, v)
294308

295309
def set_cls(self, cls: Union[tuple[str], list[str], str]):
296310
if isinstance(cls, (tuple, list)):

0 commit comments

Comments
 (0)