Skip to content
This repository has been archived by the owner on Jan 25, 2025. It is now read-only.

Commit

Permalink
Switched to try/except
Browse files Browse the repository at this point in the history
  • Loading branch information
HalfWhitt committed Jan 2, 2025
1 parent c18d7df commit e5d00ec
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 63 deletions.
43 changes: 13 additions & 30 deletions src/travertino/node.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from inspect import signature


class Node:
def __init__(self, style, applicator=None, children=None):
# Parent needs to be primed before style is (potentially) applied with
Expand Down Expand Up @@ -173,43 +170,29 @@ def refresh(self, viewport):
self._root.refresh(viewport)
else:
if self.applicator:

######################################################################
# 2024-12: Backwards compatibility for Toga <= 0.4.8
######################################################################
# (See below)
if self._old_layout_args():
self.style.layout(self, viewport)
else:
# Accommodate the earlier signature of layout(), which included the node
# as a parameter.
try:
self.style.layout(viewport)

except TypeError as error:
if (
".layout() missing 1 required positional argument: 'viewport'"
in str(error)
):
self.style.layout(self, viewport)
else:
raise
######################################################################
# End backwards compatibility
######################################################################

self.applicator.set_bounds()

######################################################################
# 2024-12: Backwards compatibility for Toga <= 0.4.8
######################################################################

# Accommodate the earlier signature of layout(), which included the node as a
# parameter. This needs to be called on the *instance* -- to have access to the
# style -- but it needs to be cached on the *class*, so all instances have access
# to it.

def _old_layout_args(self):
try:
return self.__class__._cached_old_layout_args
except AttributeError:
self.__class__._cached_old_layout_args = (
"node" in signature(self.style.layout).parameters
)

return self.__class__._cached_old_layout_args

######################################################################
# End backwards compatibility
######################################################################

def _set_root(self, node, root):
# Propagate a root node change through a tree.
node._root = root
Expand Down
58 changes: 25 additions & 33 deletions tests/test_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ def layout(self, node, viewport):
super().layout(viewport)


class TypeErrorStyle(Style):
# Uses the correct signature, but raises an unrelated TypeError in layout
def layout(self, viewport):
raise TypeError("An unrelated TypeError has occurred somewhere in layout()")


class OldTypeErrorStyle(Style):
# Just to be extra safe...
def layout(self, node, viewport):
raise TypeError("An unrelated TypeError has occurred somewhere in layout()")


@prep_style_class
class BrokenStyle(BaseStyle):
def reapply(self):
Expand Down Expand Up @@ -136,39 +148,6 @@ def test_create_node():
assert child3.root == new_node


@pytest.mark.parametrize(
"StyleClass, cached_value",
[
(Style, False),
(OldStyle, True),
],
)
def test_layout_signature_check(StyleClass, cached_value):
"""After the first call to refresh(), node class should cache args version."""

class Applicator:
def set_bounds(self):
pass

class TestNode(Node):
# So we don't change the actual Node class
pass

# Before refresh() is called, the cached value isn't set.
assert not hasattr(TestNode, "_cached_old_layout_args")

# Check again, just to be sure nothing has changed from creating an instance.
node = TestNode(style=StyleClass(), applicator=Applicator())
assert not hasattr(TestNode, "_cached_old_layout_args")

# Refresh for the first time.
node.refresh(Viewport(width=10, height=20))

# After refreshing, which signature to use for layout() should be cached on the
# class, and thus accessible to both instances.
assert TestNode._cached_old_layout_args == cached_value


@pytest.mark.parametrize("StyleClass", [Style, OldStyle])
def test_refresh(StyleClass):
"""The layout can be refreshed, and the applicator invoked"""
Expand Down Expand Up @@ -224,6 +203,19 @@ def __init__(self, style, children=None):
assert child3.applicator.tasks == []


@pytest.mark.parametrize("StyleClass", [TypeErrorStyle, OldTypeErrorStyle])
def test_type_error_in_layout(StyleClass):
"""The shim shouldn't hide unrelated TypeErrors."""

class Applicator:
def set_bounds(self):
pass

node = Node(style=StyleClass(), applicator=Applicator())
with pytest.raises(TypeError, match=r"unrelated TypeError"):
node.refresh(Viewport(50, 50))


def test_add():
"""Nodes can be added as children to another node"""

Expand Down

0 comments on commit e5d00ec

Please sign in to comment.