Symptom
An import of a module-level VALUE carries no pointer to what it names. The parser
resolves it to resolvedTargetKind=MODULE with an EMPTY resolvedTargetHash, so nothing
downstream can reach the declaration.
# signals.py
class Signal: ...
order_placed = Signal()
# publisher.py
from signals import order_placed # -> MODULE, hash ""
A def or a class imported the same way resolves correctly, with a real hash.
Root cause
The member lookup that runs for a from X import Y statement resolves Y against the
functions and classes declared in X. A module-level assignment is neither, so the lookup
falls through to the module itself and the target hash is left empty. The kind reported is
then MODULE, which is accurate for what was found and wrong for what was written: the
import names a member, not a module.
Census
Counted over one mid-sized public project's parse, by resolvedTargetKind:
| kind |
rows |
with an empty hash |
| UNRESOLVED |
1859 |
1859 |
| MODULE |
1225 |
1225 |
| TYPE |
817 |
0 |
| FUNCTION |
708 |
0 |
Every MODULE-kind row, without exception. Roughly 940 of the 1225 are value-shaped: a
lowercase member imported from a client module, such as from pkg._state import current_app. TYPE and FUNCTION never have an empty hash, so the gap is specific to the
value case rather than general to imports.
Why it matters
Any engine rule that joins two declarations through an imported module-level object stops
at the boundary. That covers a signal object, a settings singleton, a module-level
registry dict and a configured client instance. The rule cannot tell that two modules
importing the same name are talking about the same object, because neither import carries
its identity.
A concrete case is already shipped and pinned. In
graph/python/engine/framework-behavior/dispatch.dl, signal_dispatch joins a publisher
to a receiver by requiring both ends to resolve to the same binding for the signal object.
Across modules each end has its own import binding and the join produces nothing, so the
mechanism is same-module only. graph/test/python/cases/21-url-and-signal-dispatch asserts
that limitation in its golden: three modules sharing one signal produce no edge, and a fix
here will visibly move that file.
The sibling mechanisms are unaffected, which isolates the gap: task_dispatch joins on an
imported def and works across modules, because FUNCTION imports carry a hash.
What would resolve it
A member lookup that also resolves a module-level assignment target, so a value import
reports the binding it names rather than the module it came from. Failing that, any
durable identity for the member would let the engine join by it: the qualified name of the
declaring module plus the member name is already enough, and is how library linking joins
today.
Symptom
An import of a module-level VALUE carries no pointer to what it names. The parser
resolves it to
resolvedTargetKind=MODULEwith an EMPTYresolvedTargetHash, so nothingdownstream can reach the declaration.
A
defor aclassimported the same way resolves correctly, with a real hash.Root cause
The member lookup that runs for a
from X import Ystatement resolves Y against thefunctions and classes declared in X. A module-level assignment is neither, so the lookup
falls through to the module itself and the target hash is left empty. The kind reported is
then
MODULE, which is accurate for what was found and wrong for what was written: theimport names a member, not a module.
Census
Counted over one mid-sized public project's parse, by
resolvedTargetKind:Every MODULE-kind row, without exception. Roughly 940 of the 1225 are value-shaped: a
lowercase member imported from a client module, such as
from pkg._state import current_app. TYPE and FUNCTION never have an empty hash, so the gap is specific to thevalue case rather than general to imports.
Why it matters
Any engine rule that joins two declarations through an imported module-level object stops
at the boundary. That covers a signal object, a settings singleton, a module-level
registry dict and a configured client instance. The rule cannot tell that two modules
importing the same name are talking about the same object, because neither import carries
its identity.
A concrete case is already shipped and pinned. In
graph/python/engine/framework-behavior/dispatch.dl,signal_dispatchjoins a publisherto a receiver by requiring both ends to resolve to the same binding for the signal object.
Across modules each end has its own import binding and the join produces nothing, so the
mechanism is same-module only.
graph/test/python/cases/21-url-and-signal-dispatchassertsthat limitation in its golden: three modules sharing one signal produce no edge, and a fix
here will visibly move that file.
The sibling mechanisms are unaffected, which isolates the gap:
task_dispatchjoins on animported
defand works across modules, because FUNCTION imports carry a hash.What would resolve it
A member lookup that also resolves a module-level assignment target, so a value import
reports the binding it names rather than the module it came from. Failing that, any
durable identity for the member would let the engine join by it: the qualified name of the
declaring module plus the member name is already enough, and is how library linking joins
today.