Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion dimos/core/coordination/module_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,12 @@ def _resolve_single_ref(
is_class_ref = is_module_type(spec)

def satisfies(cls: type) -> bool:
return cls is spec if is_class_ref else spec_structural_compliance(cls, spec)
# A subclass IS-A the declared provider, so a deployment that swaps in a
# subclass (extra ports, per-instance I/O) still satisfies the ref. Exact
# identity would resolve to None here, silently.
if is_class_ref:
return isinstance(cls, type) and issubclass(cls, spec)
Comment thread
mustafab0 marked this conversation as resolved.
return spec_structural_compliance(cls, spec)

def module_of(candidate: Any) -> type[ModuleBase]:
return candidate.module if isinstance(candidate, BlueprintAtom) else candidate
Expand Down
25 changes: 25 additions & 0 deletions dimos/core/coordination/test_module_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ def start(self) -> None:
def stop(self) -> None: ...


# How a deployment specializes a provider: extra I/O, same RPC surface.
class Calculator1WithPort(Calculator1):
extra_in: In[Image]


def _build_without_rerun(blueprint: Blueprint) -> ModuleCoordinator:
"""Build with a parsed viewer override so tests never spawn Rerun."""
parsed = BlueprintConfigParser(blueprint).parse(
Expand Down Expand Up @@ -496,6 +501,26 @@ def test_module_ref_direct() -> None:
coordinator.stop()


def test_module_ref_direct_accepts_a_subclass_provider() -> None:
# A deployment may swap in a subclass to add per-instance ports. Matching
# the ref by exact class identity would leave Mod1.calc set to None, with
# no error at wiring time and an AttributeError at first use.
coordinator = _build_without_rerun(
autoconnect(
Calculator1WithPort.blueprint(),
Mod1.blueprint(),
)
)

try:
mod1 = coordinator.get_instance(Mod1)
assert mod1 is not None
assert mod1.calc is not None
assert mod1.calc.compute1(2, 3) == 5
finally:
coordinator.stop()


def test_module_ref_spec() -> None:
coordinator = _build_without_rerun(
autoconnect(
Expand Down
Loading