diff --git a/corrscope/channel.py b/corrscope/channel.py index 3a0d456..45ef4e2 100644 --- a/corrscope/channel.py +++ b/corrscope/channel.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional, Union, Dict, Any import attr from ruamel.yaml.comments import CommentedMap -from corrscope.config import DumpableAttrs, Alias, CorrError +from corrscope.config import DumpableAttrs, Alias, CorrError, evolve_compat from corrscope.triggers import MainTriggerConfig from corrscope.util import coalesce from corrscope.wave import Wave, Flatten @@ -88,12 +88,15 @@ class Channel: # Create a Trigger object. if isinstance(cfg.trigger, MainTriggerConfig): tcfg = cfg.trigger + elif isinstance( cfg.trigger, (CommentedMap, dict) ): # CommentedMap may/not be subclass of dict. - tcfg = attr.evolve(corr_cfg.trigger, **cfg.trigger) + tcfg = evolve_compat(corr_cfg.trigger, **cfg.trigger) + elif cfg.trigger is None: tcfg = corr_cfg.trigger + else: raise CorrError( f"invalid per-channel trigger {cfg.trigger}, type={type(cfg.trigger)}, " diff --git a/corrscope/config.py b/corrscope/config.py index bc3b7c3..f4df35e 100644 --- a/corrscope/config.py +++ b/corrscope/config.py @@ -23,6 +23,7 @@ __all__ = [ "yaml", "copy_config", "DumpableAttrs", + "evolve_compat", "KeywordAttrs", "with_units", "get_units", @@ -190,10 +191,14 @@ class DumpableAttrs: # SafeConstructor.construct_yaml_object() uses __setstate__ to load objects. def __setstate__(self, state: Dict[str, Any]) -> None: + self.__dict__ = self.new_from_state(state).__dict__ + + # If called via instance, cls == type(self). + @classmethod + def new_from_state(cls: Type[T], state: Dict[str, Any]) -> T: """ Redirect `Alias(key)=value` to `key=value`. Then call the dataclass constructor (to validate parameters). """ - cls = type(self) cls_name = cls.__name__ fields = attr.fields_dict(cls) @@ -225,8 +230,14 @@ class DumpableAttrs: new_state[key] = value del state - obj = cls(**new_state) - self.__dict__ = obj.__dict__ + return cls(**new_state) + + +def evolve_compat(obj: DumpableAttrs, **changes): + """Evolve an object, based on user-specified dict, + while ignoring unrecognized keywords.""" + # In dictionaries, later values will always override earlier ones + return obj.new_from_state({**obj.__dict__, **changes}) class KeywordAttrs(DumpableAttrs):