diff --git a/src/experimaestro/core/types.py b/src/experimaestro/core/types.py index 6a8fd06d..b507edaa 100644 --- a/src/experimaestro/core/types.py +++ b/src/experimaestro/core/types.py @@ -60,6 +60,12 @@ def __hash__(self): def __eq__(self, other): return other.name.__eq__(self.name) + def __getstate__(self): + return self.name + + def __setstate__(self, state): + self.name = state + def __getattr__(self, key): return self(key) diff --git a/src/experimaestro/notifications.py b/src/experimaestro/notifications.py index 798bbf1b..ccd285c7 100644 --- a/src/experimaestro/notifications.py +++ b/src/experimaestro/notifications.py @@ -153,6 +153,9 @@ def progress(value: float, level=0, desc: Optional[str] = None, console=False): if TaskEnv.instance().slave: # Skip if in a slave process return + if TaskEnv.instance().taskpath is None: + # Skip progress reporting if running outside an experimaestro task (e.g. unit tests or standalone scripts) + return Reporter.instance().set_progress(value, level, desc, console=console) diff --git a/src/experimaestro/tests/test_grid_validation.py b/src/experimaestro/tests/test_grid_validation.py index a7d49be7..bf42fd01 100644 --- a/src/experimaestro/tests/test_grid_validation.py +++ b/src/experimaestro/tests/test_grid_validation.py @@ -2,7 +2,7 @@ from experimaestro.experiments.grid import GridSearch, GenericParams from experimaestro.experiments.configuration import ConfigurationBase, configuration from experimaestro.utils.config import validate_attrs - +import pytest @configuration() class SubConfig: @@ -166,3 +166,14 @@ class CustomConfig(ConfigurationBase): assert tags[0] == {"lr": 0.05, "batch_size": 16} assert tags[1] == {"lr": 0.005, "batch_size": 64} + + +def test_error_attrs_raises_on_unrecognized_keys(): + data = { + "id": "test", + "lr": 0.05, + "unexpected_key_xyz": 123, + } + + with pytest.raises(ValueError, match="unexpected_key_xyz"): + validate_attrs(MainConfig, data) diff --git a/src/experimaestro/tests/test_objects.py b/src/experimaestro/tests/test_objects.py index c1672cd8..27c04bf0 100644 --- a/src/experimaestro/tests/test_objects.py +++ b/src/experimaestro/tests/test_objects.py @@ -438,4 +438,3 @@ def test_field_names_and_load_objects_missing_defaults(): # In-place mutations on default containers work as expected inst.items.append("test") assert inst.items == ["test"] - diff --git a/src/experimaestro/tests/test_serializers.py b/src/experimaestro/tests/test_serializers.py index 712a97cf..ac9e3ca7 100644 --- a/src/experimaestro/tests/test_serializers.py +++ b/src/experimaestro/tests/test_serializers.py @@ -257,8 +257,7 @@ def test_taskstub_serialization_roundtrip(): [reloaded_config] = from_state_dict(data2, partial_loading=False) assert isinstance(reloaded_config.__xpm__.task, TaskStub) assert ( - reloaded_config.__xpm__.task.identifier - == loaded_config.__xpm__.task.identifier + reloaded_config.__xpm__.task.identifier == loaded_config.__xpm__.task.identifier ) assert reloaded_config.__xpm__.task.typename == loaded_config.__xpm__.task.typename diff --git a/src/experimaestro/utils/config.py b/src/experimaestro/utils/config.py index 4fb92a13..3a89949c 100644 --- a/src/experimaestro/utils/config.py +++ b/src/experimaestro/utils/config.py @@ -157,11 +157,11 @@ def wrap_type(t: Any) -> Any: # Create a dynamic Pydantic model PydanticModel = create_model( cls.__name__, - __config__=ConfigDict(extra="allow", arbitrary_types_allowed=True), + __config__=ConfigDict(extra="forbid", arbitrary_types_allowed=True), **model_fields, ) - # Validate + # Validate - will raise ValidationError if invalid keys or types are present validated_model = PydanticModel(**data) # Extract only the fields that the attrs class expects