Skip to content
Open
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
18 changes: 16 additions & 2 deletions dimos/core/coordination/blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ class BlueprintAtom:
module: type[ModuleBase]
streams: tuple[StreamRef, ...]
module_refs: tuple[ModuleRef, ...]
# new
namespace: str | None = None
# end

@classmethod
def create(cls, module: type[ModuleBase], kwargs: dict[str, Any]) -> Self:
Expand Down Expand Up @@ -176,6 +179,14 @@ def create(cls, module: type[ModuleBase], **kwargs: Any) -> "Blueprint":
blueprint = BlueprintAtom.create(module, kwargs)
return cls(blueprints=(blueprint,))

# new
def namespace(self, ns: str) -> "Blueprint":
"""Recursively apply namespace to all blueprint atoms."""
new_atoms = tuple(replace(a, namespace=ns) for a in self.blueprints)
return replace(self, blueprints=new_atoms)

# end

def disabled_modules(self, *modules: type[ModuleBase]) -> "Blueprint":
return replace(self, disabled_modules_tuple=self.disabled_modules_tuple + modules)

Expand Down Expand Up @@ -251,7 +262,10 @@ def _eliminate_duplicates(blueprints: list[BlueprintAtom]) -> list[BlueprintAtom
seen = set()
unique_blueprints = []
for bp in reversed(blueprints):
if bp.module not in seen:
seen.add(bp.module)
# new
key = (bp.namespace, bp.module)
if key not in seen:
seen.add(key)
unique_blueprints.append(bp)
# end
return list(reversed(unique_blueprints))
367 changes: 226 additions & 141 deletions dimos/core/coordination/module_coordinator.py

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions dimos/core/coordination/test_module_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ def test_load_blueprint_conflict_with_existing() -> None:
"""Loading a blueprint whose stream name clashes (different type) raises ValueError."""
from dimos.core.transport import pLCMTransport

registry: dict[tuple[str, type], object] = {("data1", Data1): pLCMTransport("/data1")}
registry: dict[tuple[str | None, str, type], object] = {(None, "data1", Data1): pLCMTransport("/data1")}

class ConflictModule(Module):
data1: In[Data2] # same name, different type
Expand Down Expand Up @@ -613,12 +613,12 @@ def test_restart_module_preserves_stream_wiring(dynamic_coordinator) -> None:
c = dynamic_coordinator.get_instance(ModuleC)
assert c is not None
topic_before = c.data3.transport.topic
registry_before = dynamic_coordinator._transport_registry[("data3", Data3)]
registry_before = dynamic_coordinator._transport_registry[(None, "data3", Data3)]

dynamic_coordinator.restart_module(ModuleC, reload_source=False)

# Transport in the registry is the same parent-side object.
assert dynamic_coordinator._transport_registry[("data3", Data3)] is registry_before
assert dynamic_coordinator._transport_registry[(None, "data3", Data3)] is registry_before

c_after = dynamic_coordinator.get_instance(ModuleC)
assert c_after is not None
Expand Down Expand Up @@ -765,12 +765,12 @@ def test_restart_preserves_remapped_streams(dynamic_coordinator) -> None:
dynamic_coordinator.load_blueprint(bp)

target = dynamic_coordinator.get_instance(TargetModule)
registry_before = dynamic_coordinator._transport_registry[("remapped_data", Data1)]
registry_before = dynamic_coordinator._transport_registry[(None, "remapped_data", Data1)]

dynamic_coordinator.restart_module(SourceModule, reload_source=False)

# The coordinator-side transport object in the registry is unchanged.
assert dynamic_coordinator._transport_registry[("remapped_data", Data1)] is registry_before
assert dynamic_coordinator._transport_registry[(None, "remapped_data", Data1)] is registry_before
# The restarted proxy sees the same topic as the target.
source_after = dynamic_coordinator.get_instance(SourceModule)
assert source_after.color_image.transport.topic == target.remapped_data.transport.topic
Expand All @@ -789,4 +789,4 @@ def test_list_module_names(dynamic_coordinator) -> None:
assert dynamic_coordinator.list_module_names() == []
dynamic_coordinator.load_module(ModuleA)
dynamic_coordinator.load_module(ModuleC)
assert set(dynamic_coordinator.list_module_names()) == {"ModuleA", "ModuleC"}
assert set(dynamic_coordinator.list_module_names()) == {"ModuleA", "ModuleC"}
8 changes: 7 additions & 1 deletion dimos/core/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ class ModuleBase(Configurable, CompositeResource):
_tools_lock: threading.Lock

def __init__(self, config_args: dict[str, Any]) -> None:
# new
self._dimos_rpc_name = config_args.pop("__dimos_rpc_name__", None)
self._dimos_namespace = config_args.pop("__dimos_namespace__", None)
# end
super().__init__(**config_args)
self._module_closed_lock = threading.Lock()
self._tools = {}
Expand All @@ -154,7 +158,9 @@ def __init__(self, config_args: dict[str, Any]) -> None:
rpc_timeouts=self.config.rpc_timeouts,
default_rpc_timeout=self.config.default_rpc_timeout,
)
self.rpc.serve_module_rpc(self)
# new
self.rpc.serve_module_rpc(self, name=self._dimos_rpc_name or self.name)
# end
self.rpc.start() # type: ignore[attr-defined]
except ValueError:
...
Expand Down
16 changes: 16 additions & 0 deletions dimos/core/tests/test_blueprint_namespaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from dimos.core.coordination.blueprints import autoconnect
from dimos.core.coordination.test_blueprints import ModuleA, ModuleB


def test_namespaces_allow_duplicates_and_scope(dynamic_coordinator):
"""
verify that we can deploy the same module classes multiple times as long as they are isolated within different namespaces.
"""
robot1_bp = autoconnect(ModuleA.blueprint(), ModuleB.blueprint()).namespace("robot1")
robot2_bp = autoconnect(ModuleA.blueprint(), ModuleB.blueprint()).namespace("robot2")
system_bp = autoconnect(robot1_bp, robot2_bp)
assert len(system_bp.blueprints) == 4, "system blueprint should contain exactly 4 atoms"
dynamic_coordinator.load_blueprint(system_bp)
assert dynamic_coordinator.n_modules == 4, (
"coordinator should be tracking exactly 4 active modules"
)
6 changes: 3 additions & 3 deletions dimos/porcelain/local_module_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ def list_module_names(self) -> list[str]:
return self._coordinator.list_module_names()

def get_module(self, name: str) -> Any:
for cls, proxy in self._coordinator._deployed_modules.items():
if cls.__name__ == name:
for key, proxy in self._coordinator._deployed_modules.items():
if key.module.__name__ == name:
return proxy
raise KeyError(name)

def invalidate(self, name: str) -> None:
return None

def close(self) -> None:
return None
return None
2 changes: 1 addition & 1 deletion dimos/porcelain/test_dimos.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,4 @@ def test_connected_dir(client):

def test_connected_skills(client):
result = client.skills.ping()
assert result == "pong"
assert result == "pong"
Loading