Merging and typed results
Merge strategies, typed hydration and dot access, all importable from
anycfg. See Merging and
Typed results.
Merging
anycfg.DELETE = _Delete()
module-attribute
anycfg.INHERIT = _Inherit()
module-attribute
anycfg.Merge
Bases: Protocol
A merge strategy: combine a with the later document b.
__call__(a, b, *, memo=None, **options)
Source code in src/anycfg/_merge.py
129 130 131 132 133 134 135 136 | |
anycfg.register_merge(name, strategy, *, replace=False)
Make strategy selectable as merge=name.
Raises ValueError when name is taken, unless replace is true.
Source code in src/anycfg/_merge.py
419 420 421 422 423 424 425 426 427 428 429 430 431 432 | |
anycfg.merge_strategies()
Every registered strategy name, sorted.
Source code in src/anycfg/_merge.py
414 415 416 | |
Typed results
anycfg.hydrate(cls, data, *, extra='ignore')
Build one cls from data, coercing every field through its annotation.
The single-layer form of :func:typed_merge, and what
load(..., into=cls) uses: a dataclass, a namespace, a mapping type, a
TypedDict, a Pydantic model (validated by Pydantic) or any class whose
constructor takes the fields as keywords.
Raises:
| Type | Description |
|---|---|
ConfigTypeError
|
When data cannot be built into cls, for an
unknown key under |
ConfigValueError
|
When a value cannot be coerced, or data contains itself. |
Source code in src/anycfg/_hydrate.py
951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 | |
anycfg.dehydrate(obj, memo=None)
obj as plain data a format can write: the inverse of :func:hydrate.
A type with a _dump_() method gives its own plain form; a dataclass, a
namespace or another object with fields becomes a mapping; an Enum
member its value; a path, a Decimal or a UUID its text; mappings, lists
and tuples are walked.
Anything else is returned as it is, for the format to accept or refuse.
Aliases and cycles are kept.
Source code in src/anycfg/_hydrate.py
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 | |
anycfg.typed_merge(cls, *objects, init=True, extra='ignore')
typed_merge(
cls: "typing.Type[T]",
*,
init: bool = True,
extra: str = "ignore"
) -> None
typed_merge(
cls: "typing.Type[T]",
*objects: object,
init: bool = True,
extra: str = "ignore"
) -> T
typed_merge(
cls: typing.Any,
*objects: object,
init: bool = True,
extra: str = "ignore"
) -> typing.Any
Merge objects, in order, into one instance of cls.
Type-guided: a mapping, dataclass or namespace merges field by field, each
field through its own annotation; a sequence or set takes the last layer's
value with every item coerced; a scalar takes the last value, coerced
through the hint. None layers are skipped at every level, so a later
None never overrides an earlier value; all-None (or no) layers give
None. A layer of another shape replaces everything before it. A node
reached twice is built once; one that contains itself raises
ConfigValueError.
Coercion never loses data: int accepts a whole-number float or an
integer string, never 1.9 or a bool; bool reads true/yes/on/1
and false/no/off/0; date/datetime/time read ISO strings;
an Enum takes a value, then a member name; a Literal takes one of
its values; a NewType coerces as its base type. A NamedTuple
accepts a sequence or a mapping of its fields. A Pydantic model's layers
are deep-merged and validated by Pydantic.
extra decides what a key the target class (or TypedDict) does not accept
does: "ignore" drops it, "forbid" raises ConfigTypeError naming
it. A constructor taking **kwargs accepts every key. init false
builds the instance without calling __init__.
Hooks: a classmethod __merge__(cls, *objects, init=True) takes over a
type's merging entirely (see OpaqueMerge); a classmethod
_load_(cls, value) builds the type from one value (a scalar as written
in the document when that is known, otherwise plain data); a
_parse_<field>(value) method coerces that field as it is collected
from each layer. Without a hook the basic coercions above apply.
Source code in src/anycfg/_hydrate.py
912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 | |
anycfg.OpaqueMerge
Mixin marking a type opaque to :func:typed_merge: the last layer wins.
For a fully built object whose __init__ already normalized its fields,
and which should not be re-merged field by field — including under
Optional[...] and other unions.
__merge__(*objects, init=True)
classmethod
Source code in src/anycfg/_hydrate.py
1032 1033 1034 | |
anycfg.opaque(cls)
Class decorator equivalent of :class:OpaqueMerge, leaving the bases alone.
Source code in src/anycfg/_hydrate.py
1037 1038 1039 1040 | |
anycfg.TypedNamespace(**kwargs)
Bases: Namespace
An :class:argparse.Namespace that applies _parse_<field> coercers.
At construction each keyword's value goes through a _parse_<name>
method when the class defines one, so a built instance is already
normalized. :func:typed_merge applies the same hooks to raw layers, skips
them for a layer that is already a TypedNamespace, and assembles the
result without calling __init__ — so every value is parsed exactly once
and a parser need not be idempotent.
Source code in src/anycfg/_hydrate.py
1054 1055 1056 1057 1058 1059 | |
Dot access
anycfg.DotAccessibleDict(*args, **kwargs)
Bases: dict
Dictionary subclass supporting dot-notation queries and attribute access.
Nested mappings are converted once, at construction, so item access,
attribute access and object identity all agree however the value is
reached, whatever the read order. Reads never write: get() follows
dict.get's contract and a miss returns the default without storing it.
Values assigned after construction are stored exactly as given — a plain
dict written with cfg["x"] = {...} stays a plain dict.
A key that collides with something the class defines (items, get,
copy, ...) is reachable only through item access: attribute reads
find the method, so attribute writes and deletes of such a name raise
AttributeError rather than letting the two disagree. A subclass that
needs a real instance attribute uses object.__setattr__.
dict(cfg) is the way to get a plain dict back.
Source code in src/anycfg/_dotaccess.py
84 85 86 87 88 89 90 91 92 | |
__delattr__(name)
Source code in src/anycfg/_dotaccess.py
114 115 116 117 118 119 120 121 122 123 124 125 | |
__getattr__(name)
Source code in src/anycfg/_dotaccess.py
94 95 96 97 98 99 100 | |
__or__(other)
Source code in src/anycfg/_dotaccess.py
131 132 133 134 135 136 137 138 139 140 141 | |
__ror__(other)
Source code in src/anycfg/_dotaccess.py
143 144 145 146 147 148 149 150 | |
__setattr__(name, value)
Source code in src/anycfg/_dotaccess.py
102 103 104 105 106 107 108 109 110 111 112 | |
copy()
A shallow copy of the same class — like dict.copy, but typed.
Source code in src/anycfg/_dotaccess.py
127 128 129 | |
dig(*keys, default=None)
:func:anycfg.dig on this mapping: keys as given, a miss is default.
Source code in src/anycfg/_dotaccess.py
219 220 221 | |
get(key, default=None, dig=True)
Look up key, optionally as a path into nested containers.
The order is: an exact key wins; then, when dig is true, a dotted-string or tuple path is traversed; otherwise default.
A dotted string ("database.credentials.user") walks mappings by
segment. A segment of ASCII digits indexes a list or tuple
("servers.0.host"), but on a mapping it is always the string key,
so "codes.0" never means the integer 0. Exact-key precedence
applies at the top level only, so a nested key that itself contains a
dot is unreachable this way — pass a tuple path for that
(("metadata", "labels", "app.kubernetes.io/name")), which also
reaches integer keys and uses ints, not digit strings, as indexes.
A None (or any non-traversable value) reached with segments still
to go yields default; the final segment's value is returned as
stored, None included — so an explicit null stays
distinguishable from an absent key.
Never inserts: a miss returns default itself, as dict.get does.
Source code in src/anycfg/_dotaccess.py
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | |