Proxy that enables dot-notation and bracket-notation access to nested config structures. When :meth:`ConfigParser.__getattr__` resolves to a ``dict`` or ``list``, the result is wrapped in this proxy so that further attribute and index access chains through the config hierarchy usin
| 60 | |
| 61 | |
| 62 | class _ConfigProxy: |
| 63 | """ |
| 64 | Proxy that enables dot-notation and bracket-notation access to nested config structures. |
| 65 | |
| 66 | When :meth:`ConfigParser.__getattr__` resolves to a ``dict`` or ``list``, the result is |
| 67 | wrapped in this proxy so that further attribute and index access chains through the |
| 68 | config hierarchy using :meth:`ConfigParser.get_parsed_content`. For example:: |
| 69 | |
| 70 | parser.training.trainer.max_epochs |
| 71 | # equivalent to |
| 72 | parser.get_parsed_content("training::trainer::max_epochs") |
| 73 | |
| 74 | parser.transforms[0].keys # list indexing chains too |
| 75 | parser.A.B["C"] = 99 # writes update the config source |
| 76 | del parser.A.B["C"] # deletes update the config source |
| 77 | |
| 78 | Type caveat: |
| 79 | Accessing a ``dict``/``list`` member through a :class:`ConfigParser` now returns a |
| 80 | ``_ConfigProxy``, not the raw container, so ``type(parser.A)`` is ``_ConfigProxy`` |
| 81 | and ``isinstance(parser.A, dict)`` is ``False``. Code that needs the real container |
| 82 | should use ``parser.A._raw`` (read-only view) or ``parser.get_parsed_content("A")``. |
| 83 | |
| 84 | Precedence and fallback: |
| 85 | Config keys take precedence over ``dict``/``list`` attributes and methods. If a |
| 86 | config key is not found, the proxy falls back to the underlying ``dict``/``list`` |
| 87 | so that container methods (``.keys()``, ``.items()`` ...) and native indexing |
| 88 | semantics (``IndexError``, negative indices, dict ``KeyError``) still work. A |
| 89 | config key that collides with a container method name (e.g. ``"keys"``) shadows |
| 90 | that method on attribute access; access it via bracket notation, |
| 91 | :meth:`ConfigParser.get_parsed_content`, or ``._raw``. |
| 92 | |
| 93 | Writes: |
| 94 | ``__setitem__``/``__setattr__``/``__delitem__``/``__delattr__`` write through to |
| 95 | the config *source* (via :class:`ConfigParser`) and reset the reference resolver, |
| 96 | so the change is visible from both ``parser.<id>`` and |
| 97 | ``parser.get_parsed_content("<id>")``. |
| 98 | """ |
| 99 | |
| 100 | _INTERNAL = ("_parser", "_id", "_value") |
| 101 | |
| 102 | def __init__(self, parser: ConfigParser, id: str, value: Any): |
| 103 | """ |
| 104 | Args: |
| 105 | parser: the owning :class:`ConfigParser`. |
| 106 | id: the ``::``-separated id this proxy represents. |
| 107 | value: the parsed ``dict``/``list`` content this proxy wraps. |
| 108 | """ |
| 109 | self._parser = parser |
| 110 | self._id = id |
| 111 | self._value = value |
| 112 | |
| 113 | def _child_id(self, key: str | int) -> str: |
| 114 | return f"{self._id}{ID_SEP_KEY}{key}" |
| 115 | |
| 116 | def _backing_id(self) -> str: |
| 117 | """Return the real config id this proxy writes to, resolving all ``$@ref`` hops transitively.""" |
| 118 | current = self._id |
| 119 | seen: set[str] = set() |
no outgoing calls
no test coverage detected
searching dependent graphs…