Skip to content

Commit ccbbdad

Browse files
committed
Add Tag.find_by_id and use it to avoid interal cache to save memory
1 parent 9522af6 commit ccbbdad

File tree

5 files changed

+69
-88
lines changed

5 files changed

+69
-88
lines changed

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.13.0',
1919
author='CodeLV',
2020
author_email='[email protected]',
2121
url='https://github.com/codelv/enaml-web',

tests/test_html.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def test_hello_world(app):
4747
assert view.render()
4848
assert len(list(view.proxy.child_widgets())) == 2
4949
head = next(view.proxy.xpath("//head"))
50-
html = head.parent_widget()
50+
html = head.parent
5151
assert html.tag == "html"
5252

5353

web/components/html.py

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,7 @@ class ProxyTag(ProxyToolkitObject):
5959
#: Reference to the declaration
6060
declaration = ForwardTyped(lambda: Tag)
6161

62-
#: A cached reference to the root element.
63-
#: WARNING: If the root is changed this becomes invalid
64-
root = ForwardTyped(lambda: ProxyTag)
65-
66-
def xpath(self, query: str, **kwargs) -> Generator[ProxyToolkitObject, None, None]:
62+
def xpath(self, query: str, **kwargs) -> Generator["ProxyTag", None, None]:
6763
"""Perform an xpath lookup on the node"""
6864
raise NotImplementedError
6965

@@ -189,18 +185,15 @@ def _update_proxy(self, change: ChangeDict):
189185
handler(value)
190186
else:
191187
proxy.set_attribute(name, value)
192-
root = proxy.root
193-
if root is not None and root.rendered:
194-
self._notify_modified(
195-
root.declaration,
196-
{
197-
"id": self.id,
198-
"type": change["type"],
199-
"name": name,
200-
"value": value,
201-
"oldvalue": change["oldvalue"],
202-
},
203-
)
188+
self._notify_modified(
189+
{
190+
"id": self.id,
191+
"type": change["type"],
192+
"name": name,
193+
"value": value,
194+
"oldvalue": change["oldvalue"],
195+
},
196+
)
204197

205198
# =========================================================================
206199
# Object API
@@ -209,32 +202,23 @@ def _update_proxy(self, change: ChangeDict):
209202
def child_added(self, child: Declarative):
210203
super().child_added(child)
211204
if self.proxy_is_active and isinstance(child, Tag):
212-
proxy = self.proxy
213-
assert proxy is not None
214-
root = proxy.root
215-
assert root is not None
216-
if root.rendered:
217-
self._notify_modified(
218-
root.declaration,
219-
{
220-
"id": self.id,
221-
"type": "added",
222-
"name": "children",
223-
"value": child.render(),
224-
"index": self._child_index(child),
225-
},
226-
)
205+
self._notify_modified(
206+
{
207+
"id": self.id,
208+
"type": "added",
209+
"name": "children",
210+
"value": child.render(),
211+
"index": self._child_index(child),
212+
},
213+
)
227214

228215
def child_moved(self, child: Declarative):
229216
super().child_moved(child)
230217
if self.proxy_is_active and isinstance(child, Tag):
231218
proxy = self.proxy
232219
assert proxy is not None
233-
root = proxy.root
234-
assert root is not None
235-
if root.rendered and proxy.child_moved(child.proxy):
220+
if proxy.child_moved(child.proxy):
236221
self._notify_modified(
237-
root.declaration,
238222
{
239223
"id": self.id,
240224
"type": "moved",
@@ -252,28 +236,26 @@ def child_removed(self, child: Declarative):
252236
"""
253237
super().child_removed(child)
254238
if self.proxy_is_active and isinstance(child, Tag):
255-
proxy = self.proxy
256-
assert proxy is not None
257-
root = proxy.root
258-
assert root is not None
259-
if root.rendered:
260-
self._notify_modified(
261-
root.declaration,
262-
{
263-
"id": self.id,
264-
"type": "removed",
265-
"name": "children",
266-
"value": child.id,
267-
},
268-
)
269-
270-
def _notify_modified(self, root: Optional[Tag], change: dict[str, Any]):
239+
self._notify_modified(
240+
{
241+
"id": self.id,
242+
"type": "removed",
243+
"name": "children",
244+
"value": child.id,
245+
},
246+
)
247+
248+
def _notify_modified(self, change: dict[str, Any]):
271249
"""Trigger a modified event on the root node. Subclasses may override
272250
this to update change parameters if needed.
273251
274252
"""
275-
if root is not None:
276-
root.modified(change)
253+
root = self.root_object()
254+
if isinstance(root, Html):
255+
proxy = root.proxy
256+
assert proxy is not None
257+
if proxy.rendered:
258+
root.modified(change)
277259

278260
def _child_index(self, child: Tag) -> int:
279261
"""Find the index of the child ignoring any pattern nodes"""
@@ -282,7 +264,25 @@ def _child_index(self, child: Tag) -> int:
282264
# =========================================================================
283265
# Tag API
284266
# =========================================================================
285-
def xpath(self, query: str, **kwargs) -> list[Tag]:
267+
def find_by_id(self, id: str) -> Optional[Tag]:
268+
"""Find a child node with the given id.
269+
270+
Parameters
271+
----------
272+
id: str
273+
The id to look for.
274+
275+
Returns
276+
-------
277+
results: Optional[Tag]
278+
The first node with the given id or None.
279+
280+
"""
281+
for child in self.traverse():
282+
if isinstance(child, Tag) and child.id == id:
283+
return child
284+
285+
def xpath(self, query: str, **kwargs) -> list[Tag,...]:
286286
"""Find nodes matching the given xpath query
287287
288288
Parameters
@@ -300,7 +300,7 @@ def xpath(self, query: str, **kwargs) -> list[Tag]:
300300
"""
301301
proxy = self.proxy
302302
assert proxy is not None
303-
return [n.declaration for n in proxy.xpath(query, **kwargs)]
303+
return [n for n in proxy.xpath(query, **kwargs)]
304304

305305
def prepare(self, **kwargs: dict[str, Any]):
306306
"""Prepare this node for rendering.

web/components/md.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ def _update_proxy(self, change: ChangeDict):
8989
"""The superclass implementation is sufficient."""
9090
super()._update_proxy(change)
9191

92-
def _notify_modified(self, root: Optional[Tag], change: dict[str, Any]):
92+
def _notify_modified(self, change: dict[str, Any]):
9393
"""Update the notification"""
9494
if change["type"] == "update" and change["name"] == "source":
9595
change["value"] = self.render()
96-
super()._notify_modified(root, change)
96+
super()._notify_modified(change)

web/impl/lxml_toolkit_object.py

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ def create_widget(self):
6666
"""
6767
d = self.declaration
6868
parent = d.parent.proxy
69-
self.root = parent.root
70-
self.root.cache[d.id] = self
7169
self.widget = SubElement(parent.widget, d.tag)
7270

7371
def init_widget(self):
@@ -117,7 +115,7 @@ def init_widget(self):
117115

118116
for m in get_fields(d.__class__):
119117
name = m.name
120-
value = m.do_getattr(d)
118+
value = getattr(d, name)
121119
if value is True:
122120
set_attr(name, name)
123121
elif value:
@@ -145,15 +143,6 @@ def destroy(self):
145143
and set its parent to None.
146144
147145
"""
148-
if self.root is not None:
149-
if (root := self.root) and (cache := root.cache):
150-
try:
151-
del cache[self.declaration.id]
152-
except KeyError:
153-
pass
154-
155-
del self.root
156-
157146
if self.widget is not None:
158147
parent = self.widget.getparent()
159148
if parent is not None:
@@ -224,19 +213,21 @@ def render(
224213
"""Render the widget tree into a string"""
225214
return tostring(self.widget, method=method, encoding=encoding, **kwargs)
226215

227-
def xpath(self, query: str, **kwargs) -> Generator[WebComponent, None, None]:
216+
def xpath(self, query: str, **kwargs) -> Generator[ProxyTag, None, None]:
228217
"""Get the node(s) matching the query"""
229218
w = self.widget
230219
if w is None:
231220
return None
232221
nodes = w.xpath(query, **kwargs)
233222
if not nodes:
234223
return None
235-
if root := self.root:
236-
lookup = root.cache.get
237-
for node in nodes:
238-
if obj := lookup(node.get("id")):
239-
yield obj
224+
find_by_id = self.declaration.find_by_id
225+
for node in nodes:
226+
node_id = node.get("id")
227+
if not node_id:
228+
continue
229+
if obj := find_by_id(node_id):
230+
yield obj
240231

241232
def parent_widget(self) -> Optional[_Element]:
242233
"""Get the parent toolkit widget for this object.
@@ -332,24 +323,14 @@ def set_draggable(self, draggable: bool):
332323
class RootWebComponent(WebComponent):
333324
"""A root component"""
334325

335-
#: Components are cached for lookup by id so xpath queries from lxml
336-
#: can retrieve their declaration component
337-
cache = Dict()
338-
339326
#: Flag to indicate whether this node was rendered. This is used by the
340327
#: declaration to avoid creating unnecessary modified events.
341328
rendered = Bool()
342329

343330
def create_widget(self):
344331
d = self.declaration
345-
self.root = self.cache[d.id] = self
346332
self.widget = Element(d.tag)
347333

348334
def render(self, *args, **kwargs):
349335
self.rendered = True
350336
return super().render(*args, **kwargs)
351-
352-
def destroy(self):
353-
del self.root
354-
del self.cache
355-
super().destroy()

0 commit comments

Comments
 (0)