Skip to content

fix(extract): a decorator with arguments but no path is not a route - #2254

Open
DeusData wants to merge 1 commit into
mainfrom
distill/1245-patch-decorator-route
Open

DeusData wants to merge 1 commit into
mainfrom
distill/1245-patch-decorator-route

Conversation

@DeusData

Copy link
Copy Markdown
Owner

Distilled from #1245 by @ahundt (a slice of d15071c), carried with Co-authored-by — with a different rule than upstream chose. #1245 stays open until its distills land.

The bug

@patch("subprocess.run")
def test_runs(self, run): ...

decorator_method_name maps a bare patch to the HTTP verb PATCH; try_route_from_decorator_call accepts receiver-less decorators; and when no path-shaped argument is found, it falls back to "/". So every unittest.mock-decorated test function became a Route handler, PATCH "/".

The rule, and why it differs from upstream

Upstream used a has_receiver heuristic. That still admits @mock.patch("os.getcwd") (it has a receiver) and would reject the bare framework decorators Litestar and BlackSheep use (@get("/x")).

This rule is about the arguments, not the callee: a decorator call that has arguments but none path-shaped is not a route. The "/" default survives only for a zero-argument call like @app.route(), where "/" is what the framework means. I checked that no test on main relied on the default for a call with non-path arguments before changing it. The production change is three lines.

RED → GREEN

Before the fix:

extract_python_mock_patch_is_not_route         FAIL tests/test_extraction.c:4023: mocked->route_path is not NULL
extract_python_bare_decorator_route_rules      FAIL tests/test_extraction.c:4063: mocked->route_path is not NULL

The second test also pins the shapes that must keep working: bare @get("/health") still yields a GET route, zero-argument @app.route() still yields "/" ANY — those assertions passed before and after.

After: extraction 351 passed; extraction pipeline edge_types_probe route_canon cross_repo infrascan lang_contract 754 passed, CALLS-breadth contract 54 languages / 0 failures.

The single remaining red in both runs is extract_spill_round_trip_keeps_every_field refusing to spill onto a disk below its 10 GB floor — this host, not this change; it fails identically before and after.

One edge not tested: @app.route( # comment\n) — tree-sitter may count the comment as a named child of the argument list, which would make it a non-route instead of "/". Judged out of scope; flagging so it is not a surprise.

Distilled from #1245 by Andrew Hundt (a slice of d15071c), with a
different rule than upstream chose.

Python's unittest.mock is used as a decorator:

    @patch("subprocess.run")
    def test_runs(self, run): ...

decorator_method_name maps a bare `patch` to the HTTP method PATCH, and
try_route_from_decorator_call accepts a receiver-less decorator call.
When extract_route_path_from_args then finds nothing path-shaped, the
function falls back to "/". So every mock-decorated test function became
a Route handler, PATCH "/", and every one of those was wrong.

Upstream fixed it with a has_receiver heuristic. That still admits
@mock.patch("os.getcwd") (it has a receiver) and would reject the bare
framework decorators Litestar and BlackSheep use, @get("/x"). The rule
here is about the arguments, not the callee: a decorator call that HAS
arguments but none of them path-shaped is not a route. The "/" default
survives only for a zero-argument call such as @app.route(), which is the
one case where "/" is what the framework means. No test on main relied on
the default for a call with non-path arguments; checked before changing
it.

RED before the fix:
  extract_python_mock_patch_is_not_route
      FAIL tests/test_extraction.c:4023: mocked->route_path is not NULL
  extract_python_bare_decorator_route_rules
      FAIL tests/test_extraction.c:4063: mocked->route_path is not NULL

The second test also pins the two shapes that must keep working: bare
@get("/health") still yields a GET route, and zero-argument @app.route()
still yields "/" ANY -- those assertions passed before the fix and after.

GREEN after: extraction 351 passed; extraction pipeline edge_types_probe
route_canon cross_repo infrascan lang_contract 754 passed, with the
CALLS-breadth contract at 54 languages / 0 failures. The single remaining
red in both runs is extract_spill_round_trip_keeps_every_field refusing to
spill onto a disk below its 10 GB floor -- this host, not this change.

Co-authored-by: Andrew Hundt <ATHundt@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant