def _do_parse(self, config, id: str = ""): """ Recursively parse the nested data in config source, add every item as `ConfigItem` to the resolver. Args: config: config source to parse. id: id of the ``ConfigItem``, ``"#"`` in id are interpreted as special characters to go one level further into the nested structures. Use digits indexing from "0" for list or other strings for dict. For example: ``"xform#5"``, ``"net#channels"``. ``""`` indicates the entire ``self.config``. """ if isinstance(config, (dict, list)): for k, v in enumerate(config) if isinstance(config, list) else config.items(): sub_id = f"{id}{ID_SEP_KEY}{k}" if id != "" else k self._do_parse(config=v, id=sub_id) # copy every config item to make them independent and add them to the resolver item_conf = deepcopy(config) if ConfigComponent.is_instantiable(item_conf): self.ref_resolver.add_item(ConfigComponent(config=item_conf, id=id, locator=self.locator)) elif ConfigExpression.is_expression(item_conf): self.ref_resolver.add_item(ConfigExpression(config=item_conf, id=id, globals=self.globals)) else: self.ref_resolver.add_item(ConfigItem(config=item_conf, id=id))
def test_resolve(self, configs, expected_id, output_type): locator = ComponentLocator() resolver = ReferenceResolver() # add items to resolver for k, v in configs.items(): if ConfigComponent.is_instantiable(v): resolver.add_item( ConfigComponent(config=v, id=k, locator=locator)) elif ConfigExpression.is_expression(v): resolver.add_item( ConfigExpression(config=v, id=k, globals={ "monai": monai, "torch": torch })) else: resolver.add_item(ConfigItem(config=v, id=k)) result = resolver.get_resolved_content( expected_id) # the root id is `expected_id` here self.assertTrue(isinstance(result, output_type)) # test lazy instantiation item = resolver.get_item(expected_id, resolve=True) config = item.get_config() config["_disabled_"] = False item.update_config(config=config) if isinstance(item, ConfigComponent): result = item.instantiate() else: result = item.get_config() self.assertTrue(isinstance(result, output_type))
def update_config_with_refs(cls, config, id: str, refs: Optional[Dict] = None): """ With all the references in ``refs``, update the input config content with references and return the new config. Args: config: input config content to update. id: ID name for the input config. refs: all the referring content with ids, default to `None`. """ refs_: Dict = refs or {} if isinstance(config, str): return cls.update_refs_pattern(config, refs_) if not isinstance(config, (list, dict)): return config ret = type(config)() for idx, v in config.items() if isinstance(config, dict) else enumerate(config): sub_id = f"{id}{cls.sep}{idx}" if id != "" else f"{idx}" if ConfigComponent.is_instantiable(v) or ConfigExpression.is_expression(v): updated = refs_[sub_id] if ConfigComponent.is_instantiable(v) and updated is None: # the component is disabled continue else: updated = cls.update_config_with_refs(v, sub_id, refs_) ret.update({idx: updated}) if isinstance(ret, dict) else ret.append(updated) return ret
def update_refs_pattern(cls, value: str, refs: Dict) -> str: """ Match regular expression for the input string to update content with the references. The reference part starts with ``"@"``, like: ``"@XXX#YYY#ZZZ"``. References dictionary must contain the referring IDs as keys. Args: value: input value to match regular expression. refs: all the referring components with ids as keys, default to `None`. """ # regular expression pattern to match "@XXX" or "@XXX#YYY" result = cls.id_matcher.findall(value) value_is_expr = ConfigExpression.is_expression(value) for item in result: ref_id = item[len(cls.ref) :] # remove the ref prefix "@" if ref_id not in refs: msg = f"can not find expected ID '{ref_id}' in the references." if cls.allow_missing_reference: warnings.warn(msg) continue else: raise KeyError(msg) if value_is_expr: # replace with local code, will be used in the `evaluate` logic with `locals={"refs": ...}` value = value.replace(item, f"{cls._vars}['{ref_id}']") elif value == item: # the whole content is "@XXX", it will avoid the case that regular string contains "@" value = refs[ref_id] return value
def find_refs_in_config( cls, config, id: str, refs: Optional[Dict[str, int]] = None) -> Dict[str, int]: """ Recursively search all the content of input config item to get the ids of references. References mean: the IDs of other config items (``"@XXX"`` in this config item), or the sub-item in the config is `instantiable`, or the sub-item in the config is `expression`. For `dict` and `list`, recursively check the sub-items. Args: config: input config content to search. id: ID name for the input config item. refs: dict of the ID name and count of found references, default to `None`. """ refs_: Dict[str, int] = refs or {} if isinstance(config, str): for id, count in cls.match_refs_pattern(value=config).items(): refs_[id] = refs_.get(id, 0) + count if not isinstance(config, (list, dict)): return refs_ for k, v in config.items() if isinstance(config, dict) else enumerate(config): sub_id = f"{id}{cls.sep}{k}" if id != "" else f"{k}" if ConfigComponent.is_instantiable( v ) or ConfigExpression.is_expression(v) and sub_id not in refs_: refs_[sub_id] = 1 refs_ = cls.find_refs_in_config(v, sub_id, refs_) return refs_
def match_refs_pattern(cls, value: str) -> Set[str]: """ Match regular expression for the input string to find the references. The reference string starts with ``"@"``, like: ``"@XXX#YYY#ZZZ"``. Args: value: input value to match regular expression. """ refs: Set[str] = set() # regular expression pattern to match "@XXX" or "@XXX#YYY" result = cls.id_matcher.findall(value) value_is_expr = ConfigExpression.is_expression(value) for item in result: if value_is_expr or value == item: # only check when string starts with "$" or the whole content is "@XXX" refs.add(item[len(cls.ref) :]) return refs