Skip to content
Draft
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
3 changes: 2 additions & 1 deletion dimos/core/coordination/blueprint_config/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
plain,
plain_mapping,
snapshot_mapping,
validated_model_values,
)
from dimos.core.coordination.blueprints import (
Blueprint,
Expand Down Expand Up @@ -421,7 +422,7 @@ def _validate_modules(
raise BlueprintConfigError(
format_validation_error(module.atom.name, error)
) from error
dumped = model.model_dump(mode="python", exclude_unset=True)
dumped = validated_model_values(model)
dumped.pop("g", None)
dumped.pop("instance_name", None)
parsed[module.atom.name] = dumped
Expand Down
22 changes: 22 additions & 0 deletions dimos/core/coordination/blueprint_config/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
# limitations under the License.

from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
import pickle
from typing import Annotated, Any, Literal

from pydantic import BaseModel, Field
Expand Down Expand Up @@ -452,6 +454,14 @@ def __str__(self) -> str:
return "Anchor:\n multi\n line"


@dataclass(frozen=True)
class CallableAnchor:
prefix: str

def __call__(self, value: Any) -> str:
return f"{self.prefix}:{value}"


class ArbitraryConfig(ModuleConfig):
scaling: Anchor = Field(default_factory=Anchor)
hybrid: Anchor | str = "fallback"
Expand Down Expand Up @@ -490,6 +500,18 @@ def test_blueprint_pinned_arbitrary_value_survives_filtering() -> None:
assert isinstance(parsed.module_kwargs("arbitrarymodule")["scaling"], Anchor)


def test_blueprint_pinned_callable_dataclass_survives_worker_serialization() -> None:
blueprint = ArbitraryModule.blueprint(handlers={"scene": CallableAnchor("render")})

parsed = BlueprintConfigParser(blueprint).parse(environ={})
worker_kwargs = pickle.loads(pickle.dumps(parsed.module_kwargs("arbitrarymodule")))
worker_config = ArbitraryConfig.model_validate(worker_kwargs)
handler = worker_config.handlers["scene"]

assert isinstance(handler, CallableAnchor)
assert handler("apartment") == "render:apartment"


def test_format_help_uses_nested_parent_default_instance() -> None:
class NestedRequiredConfig(BaseModel):
value: int
Expand Down
25 changes: 25 additions & 0 deletions dimos/core/coordination/blueprint_config/values.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,31 @@ def plain(value: Any) -> Any:
return _copy_opaque(value)


def validated_model_values(model: BaseModel) -> dict[str, Any]:
"""Copy explicitly set validated fields without serializing runtime objects."""
return {
name: _validated_value(getattr(model, name))
for name in type(model).model_fields
if name in model.model_fields_set
}


def _validated_value(value: Any) -> Any:
if isinstance(value, BaseModel):
return validated_model_values(value)
if isinstance(value, Mapping):
return {_copy_opaque(key): _validated_value(item) for key, item in value.items()}
if isinstance(value, list):
return [_validated_value(item) for item in value]
if isinstance(value, tuple):
return tuple(_validated_value(item) for item in value)
if isinstance(value, set):
return {_validated_value(item) for item in value}
if isinstance(value, frozenset):
return frozenset(_validated_value(item) for item in value)
return _copy_opaque(value)


def deep_merge(destination: dict[str, Any], incoming: Mapping[str, Any]) -> None:
for key, value in incoming.items():
if key in destination and isinstance(destination[key], dict) and isinstance(value, Mapping):
Expand Down
Loading