Skip to content

Python: applying a decorator creates no edge — affected <decorator> returns "No affected nodes found" for every function it wraps #2154

Description

@infusini

Summary

Applying a decorator (@my_decorator) creates no edge between the decorated function and the decorator. The decorated function looks independent of the thing that wraps it, so reverse-impact queries miss it.

Reproduction

pkg/deco.py
pkg/consumer.py
# pkg/deco.py
def my_decorator(func):
    def wrapper(*a, **k):
        return func(*a, **k)
    return wrapper
# pkg/consumer.py
from pkg.deco import my_decorator

@my_decorator
def business_logic(x):
    return x * 2

graphify extract . --code-only --force on 0.9.25 — full edge list:

consumer.py --contains-----> business_logic()
consumer.py --imports_from--> deco.py
consumer.py --imports-------> my_decorator()
deco.py     --contains-----> my_decorator()

There is no business_logic() -> my_decorator() edge. The only link is the file-level import.

$ graphify affected "my_decorator" --depth 2
- consumer.py [imports] pkg/consumer.py:L1

business_logic() — the thing whose behaviour actually changes if the decorator changes — is not reported.

Why it matters

This is the standard "framework entrypoint" pattern. In our fleet, a shared package exposes its whole public API as two decorators:

# md_dataset/process.py
def md_py(func): ...      # -> get_file_manager() -> load_data(...)
def md_upload(func): ...  # -> get_file_manager().save_tables(...)

Six downstream repos consume the package only through @md_py / @md_upload. Because decoration creates no edge, reverse traversal from the decorator finds none of the functions it wraps, and:

$ graphify affected md_py --depth 2
No affected nodes found.

That is a false negative on a "what breaks?" question — cheap, fast, confident, and wrong. It is more dangerous than no answer, because an empty result reads as "nothing depends on this."

Suggested fix

When a decorated_definition / decorator list is present, emit an edge from the decorated symbol to the decorator (e.g. decorated_by, or reuse uses / calls). Python is the clearest case; the same applies to TS/JS decorators.

Possibly related: #1566 (extending indirect_call to remaining dispatch patterns) — decoration is arguably another dispatch pattern.

Environment

graphifyy 0.9.25 · macOS 15 (darwin 25.5.0) · Python 3.9 · --code-only

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions