Utility class to manage a set of ``ConfigItem`` and resolve the references between them. This class maintains a set of ``ConfigItem`` objects and their associated IDs. The IDs must be unique within this set. A string in ``ConfigItem`` starting with ``@`` will be treated as a refere
| 24 | |
| 25 | |
| 26 | class ReferenceResolver: |
| 27 | """ |
| 28 | Utility class to manage a set of ``ConfigItem`` and resolve the references between them. |
| 29 | |
| 30 | This class maintains a set of ``ConfigItem`` objects and their associated IDs. |
| 31 | The IDs must be unique within this set. A string in ``ConfigItem`` |
| 32 | starting with ``@`` will be treated as a reference to other ``ConfigItem`` objects by ID. |
| 33 | Since ``ConfigItem`` may have a nested dictionary or list structure, |
| 34 | the reference string may also contain the separator ``::`` to refer to a substructure by |
| 35 | key indexing for a dictionary or integer indexing for a list. |
| 36 | |
| 37 | In this class, resolving references is essentially substitution of the reference strings with the |
| 38 | corresponding python objects. A typical workflow of resolving references is as follows: |
| 39 | |
| 40 | - Add multiple ``ConfigItem`` objects to the ``ReferenceResolver`` by ``add_item()``. |
| 41 | - Call ``get_resolved_content()`` to automatically resolve the references. This is done (recursively) by: |
| 42 | - Convert the items to objects, for those do not have references to other items. |
| 43 | - If it is instantiable, instantiate it and cache the class instance in ``resolved_content``. |
| 44 | - If it is an expression, evaluate it and save the value in ``resolved_content``. |
| 45 | - Substitute the reference strings with the corresponding objects. |
| 46 | |
| 47 | Args: |
| 48 | items: ``ConfigItem``s to resolve, this could be added later with ``add_item()``. |
| 49 | |
| 50 | """ |
| 51 | |
| 52 | _vars = "__local_refs" |
| 53 | sep = ID_SEP_KEY # separator for key indexing |
| 54 | ref = ID_REF_KEY # reference prefix |
| 55 | # match a reference string, e.g. "@id::key", "@id::key::0", "@_target_::key" |
| 56 | id_matcher = re.compile(rf"{ref}(?:\w*)(?:{sep}\w*)*") |
| 57 | # if `allow_missing_reference` and can't find a reference ID, will just raise a warning and don't update the config |
| 58 | allow_missing_reference = allow_missing_reference |
| 59 | |
| 60 | def __init__(self, items: Sequence[ConfigItem] | None = None): |
| 61 | # save the items in a dictionary with the `ConfigItem.id` as key |
| 62 | self.items: dict[str, ConfigItem] = {} if items is None else {i.get_id(): i for i in items} |
| 63 | self.resolved_content: dict[str, ConfigExpression | str | Any | None] = {} |
| 64 | |
| 65 | def reset(self): |
| 66 | """ |
| 67 | Clear all the added `ConfigItem` and all the resolved content. |
| 68 | |
| 69 | """ |
| 70 | self.items = {} |
| 71 | self.resolved_content = {} |
| 72 | |
| 73 | def is_resolved(self) -> bool: |
| 74 | return bool(self.resolved_content) |
| 75 | |
| 76 | def add_item(self, item: ConfigItem) -> None: |
| 77 | """ |
| 78 | Add a ``ConfigItem`` to the resolver. |
| 79 | |
| 80 | Args: |
| 81 | item: a ``ConfigItem``. |
| 82 | |
| 83 | """ |
no outgoing calls
searching dependent graphs…