Fix key-loop in Tree.add to reuse existing children for literal segments after a matcher boundary
CI / Build Pip package (push) Successful in 1m57s

The key-loop unconditionally called self.parse() and overwrote
result.children[key] for literal segments after the walk-down loop
broke at a parameter. This destroyed pre-existing subtrees (with their
method children and handlers) when a second route (e.g. POST) shared
the same literal sub-segments after a parameter.

Added a children.get(key) reuse check before parse(), mirroring the
walk-down loop's child-reuse logic but extended past the boundary
where parameters live in path_matchers, not children.
This commit is contained in:
2026-07-24 12:54:07 +00:00
parent 9a314f9bd3
commit 1e1e031a56
3 changed files with 69 additions and 0 deletions
+30
View File
@@ -156,3 +156,33 @@ class TreeTest(unittest.TestCase):
with self.assertRaises(ValueError):
tree.add((p for p in ('a', '${id:int}', 'x')), HttpMethod.GET, self.handlers[1])
def test_nested_routes_different_methods_share_subtree(self):
tree = Tree()
tree.add((p for p in ('restaurants', '${id}', 'menu')), HttpMethod.GET, self.handlers[0])
tree.add((p for p in ('restaurants', '${id}', 'menu')), HttpMethod.POST, self.handlers[1])
h0 = Maybe.of_nullable(tree.get_handler('/restaurants/42/menu', HttpMethod.GET)).map(lambda it: it[0]).or_none()
h1 = Maybe.of_nullable(tree.get_handler('/restaurants/42/menu', HttpMethod.POST)).map(lambda it: it[0]).or_none()
self.assertIs(self.handlers[0], h0)
self.assertIs(self.handlers[1], h1)
def test_nested_routes_different_methods_reverse_order(self):
tree = Tree()
tree.add((p for p in ('restaurants', '${id}', 'menu')), HttpMethod.POST, self.handlers[1])
tree.add((p for p in ('restaurants', '${id}', 'menu')), HttpMethod.GET, self.handlers[0])
h0 = Maybe.of_nullable(tree.get_handler('/restaurants/42/menu', HttpMethod.GET)).map(lambda it: it[0]).or_none()
h1 = Maybe.of_nullable(tree.get_handler('/restaurants/42/menu', HttpMethod.POST)).map(lambda it: it[0]).or_none()
self.assertIs(self.handlers[0], h0)
self.assertIs(self.handlers[1], h1)
def test_nested_routes_combined_detail_and_menu_methods(self):
tree = Tree()
tree.add((p for p in ('restaurants', '${id}')), HttpMethod.GET, self.handlers[0])
tree.add((p for p in ('restaurants', '${id}', 'menu')), HttpMethod.GET, self.handlers[1])
tree.add((p for p in ('restaurants', '${id}', 'menu')), HttpMethod.POST, self.handlers[2])
h0 = Maybe.of_nullable(tree.get_handler('/restaurants/42', HttpMethod.GET)).map(lambda it: it[0]).or_none()
h1 = Maybe.of_nullable(tree.get_handler('/restaurants/42/menu', HttpMethod.GET)).map(lambda it: it[0]).or_none()
h2 = Maybe.of_nullable(tree.get_handler('/restaurants/42/menu', HttpMethod.POST)).map(lambda it: it[0]).or_none()
self.assertIs(self.handlers[0], h0)
self.assertIs(self.handlers[1], h1)
self.assertIs(self.handlers[2], h2)