Skip to content

Loading

One document at a time, in the standard Python serialization style that json and tomllib use: load takes a location, loads takes content. For several layered sources, use Loader. Each loading function has an awaitable twin; see the Async guide.

One document

anycfg.load(*sources, format=None, encoding='utf-8', policy=None, into=None, default=_MISSING, merge=None, **options)

Read one document from each source and merge them in order.

A source is a path, a pathapi Path or an open file, and each gives exactly one document. Several are merged later over earlier with merge, a strategy as :class:~anycfg.Loader takes it ("simple" when None: top-level keys update, so pass merge="deep" to merge nested sections). None is an absent layer and is skipped, so load(*default_configs("app.toml"), default={}) works; with every source None, default is the result. For globs or tolerated failures, use :class:~anycfg.Loader.

The format is format, or else the one each file name's suffix selects; a name no format claims raises UnsupportedFormatError. A str is always a location, never content (see :func:loads). Bytes are decoded with encoding, strictly, one BOM ignored. options go to the format and are checked against what it accepts. into builds that type from the result (:func:~anycfg.hydrate).

Includes, templates and commands follow policy (untrusted by default).

Raises:

Type Description
UnsupportedFormatError

When no format is named or claimed.

ParseError

When a document is not valid.

ConfigValueError

When a source gives no document or several.

TypeError

For no source, every source None without default, an unknown option, or a source that is not a location.

Source code in src/anycfg/_api.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
def load(
    *sources: _ty.Any,
    format: FormatLike = None,
    encoding: str = "utf-8",
    policy: "_ty.Optional[Policy]" = None,
    into: _ty.Any = None,
    default: _ty.Any = _MISSING,
    merge: _ty.Any = None,
    **options: _ty.Any,
) -> _ty.Any:
    """Read one document from each source and merge them in order.

    A source is a path, a pathapi ``Path`` or an open file, and each gives
    exactly one document. Several are merged later over earlier with *merge*,
    a strategy as :class:`~anycfg.Loader` takes it (``"simple"`` when `None`:
    top-level keys update, so pass ``merge="deep"`` to merge nested sections).
    ``None`` is an absent layer and is skipped, so
    ``load(*default_configs("app.toml"), default={})`` works; with every source
    ``None``, *default* is the result. For globs or tolerated failures, use
    :class:`~anycfg.Loader`.

    The format is *format*, or else the one each file name's suffix selects;
    a name no format claims raises `UnsupportedFormatError`. A ``str`` is
    always a location, never content (see :func:`loads`). Bytes are decoded
    with *encoding*, strictly, one BOM ignored. *options* go to the format
    and are checked against what it accepts. *into* builds that type from the
    result (:func:`~anycfg.hydrate`).

    Includes, templates and commands follow *policy* (untrusted by default).

    Raises:
        UnsupportedFormatError: When no format is named or claimed.
        ParseError: When a document is not valid.
        ConfigValueError: When a source gives no document or several.
        TypeError: For no source, every source ``None`` without *default*, an
            unknown option, or a source that is not a location.
    """
    return _load(
        sources,
        format=format,
        encoding=encoding,
        policy=policy,
        into=into,
        default=default,
        merge=merge,
        options=options,
    )

anycfg.loads(data, /, *, format=None, encoding='utf-8', policy=None, into=None, **options)

Parse one document from data in format.

data is str, or bytes decoded with encoding. Without format, the document must name its own: a #!<format> first line (#!json, #!/usr/bin/env yaml; removed before parsing) or a format's marker such as YAML's %YAML directive. Nothing is guessed from content that merely parses. Touches no file — except through an include, which policy governs and which resolves against the working directory.

Raises:

Type Description
UnsupportedFormatError

Without format, when the document names none.

ParseError

When data is not valid format.

TypeError

When data is not text or bytes, or for an unknown option.

Source code in src/anycfg/_api.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
def loads(
    data: _ty.Union[str, bytes, bytearray],
    /,
    *,
    format: FormatLike = None,
    encoding: str = "utf-8",
    policy: "_ty.Optional[Policy]" = None,
    into: _ty.Any = None,
    **options: _ty.Any,
) -> _ty.Any:
    """Parse one document from *data* in *format*.

    *data* is `str`, or `bytes` decoded with *encoding*. Without *format*, the
    document must name its own: a ``#!<format>`` first line (``#!json``,
    ``#!/usr/bin/env yaml``; removed before parsing) or a format's marker such
    as YAML's ``%YAML`` directive. Nothing is guessed from content that merely
    parses. Touches no file — except through an include, which *policy*
    governs and which resolves against the working directory.

    Raises:
        UnsupportedFormatError: Without *format*, when the document names none.
        ParseError: When *data* is not valid *format*.
        TypeError: When *data* is not text or bytes, or for an unknown option.
    """
    cls = None if format is None else _format_class(format)
    if not isinstance(data, (str, bytes, bytearray)):
        raise TypeError(f"loads() takes str or bytes, not {type(data).__name__}")
    # Not load(): a stray merge= or default= stays an option the format refuses.
    return _load(
        (_text(data, cls),),
        format=cls,
        encoding=encoding,
        policy=policy,
        into=into,
        default=_MISSING,
        merge=None,
        options=options,
    )

anycfg.load_all(source, /, *, format=None, encoding='utf-8', policy=None, into=None, **options)

Yield every document of source (a ----separated YAML stream).

A format with one document per file yields that one. into builds each document into that type. Otherwise as :func:load.

Source code in src/anycfg/_api.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
def load_all(
    source: _ty.Any,
    /,
    *,
    format: FormatLike = None,
    encoding: str = "utf-8",
    policy: "_ty.Optional[Policy]" = None,
    into: _ty.Any = None,
    **options: _ty.Any,
) -> _ty.Iterator[_ty.Any]:
    """Yield every document of *source* (a ``---``-separated YAML stream).

    A format with one document per file yields that one. *into* builds each
    document into that type. Otherwise as :func:`load`.
    """
    _check_location(source, "load_all")
    return _documents(
        source,
        format=format,
        encoding=encoding,
        policy=policy,
        options=options,
        single=False,
        into=into,
    )

anycfg.loads_all(data, /, *, format=None, encoding='utf-8', policy=None, into=None, **options)

Yield every document in data; otherwise as :func:loads.

Source code in src/anycfg/_api.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def loads_all(
    data: _ty.Union[str, bytes, bytearray],
    /,
    *,
    format: FormatLike = None,
    encoding: str = "utf-8",
    policy: "_ty.Optional[Policy]" = None,
    into: _ty.Any = None,
    **options: _ty.Any,
) -> _ty.Iterator[_ty.Any]:
    """Yield every document in *data*; otherwise as :func:`loads`."""
    cls = None if format is None else _format_class(format)
    if not isinstance(data, (str, bytes, bytearray)):
        raise TypeError(f"loads_all() takes str or bytes, not {type(data).__name__}")
    return load_all(
        _text(data, cls),
        format=cls,
        encoding=encoding,
        policy=policy,
        into=into,
        **options,
    )

anycfg.dump(obj, target, /, *, format=None, encoding='utf-8', **options)

Serialize obj and write it to target: a path, a pathapi Path or an open file.

The format is format, or else the one target's file name selects, the same rule :func:load uses, so dump(x, "a.json") then load("a.json") gives x back. A name no format claims raises. obj is serialized completely before target is opened, so a value the format cannot write leaves an existing file untouched. Paths are written with encoding and LF line endings; a text stream keeps its own codec.

Raises:

Type Description
UnsupportedFormatError

When no format is named or claimed, or the format cannot write.

ConfigTypeError

When obj holds a value the format cannot represent.

TypeError

When target is not a path or a writable file.

Source code in src/anycfg/_api.py
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
def dump(
    obj: _ty.Any,
    target: _ty.Any,
    /,
    *,
    format: FormatLike = None,
    encoding: str = "utf-8",
    **options: _ty.Any,
) -> None:
    """Serialize *obj* and write it to *target*: a path, a pathapi ``Path`` or an open file.

    The format is *format*, or else the one *target*'s file name selects, the
    same rule :func:`load` uses, so ``dump(x, "a.json")`` then ``load("a.json")``
    gives ``x`` back. A name no format claims raises. *obj* is serialized
    completely before *target* is opened, so a value the format cannot write
    leaves an existing file untouched. Paths are written with *encoding* and LF
    line endings; a text stream keeps its own codec.

    Raises:
        UnsupportedFormatError: When no format is named or claimed, or the
            format cannot write.
        ConfigTypeError: When *obj* holds a value the format cannot represent.
        TypeError: When *target* is not a path or a writable file.
    """
    if format is None:
        name = _target_name(target)
        if name is None:
            raise UnsupportedFormatError(
                f"cannot tell which format to write to {target!r}; pass format="
            )
        format = _formats.for_filename(name)
    text = dumps(obj, format=format, **options)
    if not text.endswith("\n"):
        text += "\n"
    if not isinstance(target, (str, bytes, os.PathLike, Path)) and callable(
        getattr(target, "write", None)
    ):
        try:
            target.write(text)
        except TypeError:
            # A binary target rejects str before writing anything.
            target.write(text.encode(encoding))
        return
    if isinstance(target, Path):
        target.write_bytes(text.encode(encoding))
        return
    if isinstance(target, (str, os.PathLike)):
        with open(os.fspath(target), "w", encoding=encoding, newline="") as handle:
            handle.write(text)
        return
    raise TypeError(
        f"dump() cannot write to a {type(target).__name__}; pass a path or an open file"
    )

anycfg.dumps(obj, /, *, format, **options)

Serialize obj as format and return the text.

obj is first turned into plain data (:func:~anycfg.dehydrate), so a dataclass, a namespace, an enum member, a path or a type with a _dump_() method writes as what it holds. options go to the format and are checked against what it accepts.

Raises:

Type Description
UnsupportedFormatError

When the format cannot write.

ConfigTypeError

When obj holds a value the format cannot represent.

Source code in src/anycfg/_api.py
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
def dumps(obj: _ty.Any, /, *, format: FormatLike, **options: _ty.Any) -> str:
    """Serialize *obj* as *format* and return the text.

    *obj* is first turned into plain data (:func:`~anycfg.dehydrate`), so
    a dataclass, a namespace, an enum member, a path or a type with a
    ``_dump_()`` method writes as what it holds. *options* go to the format and
    are checked against what it accepts.

    Raises:
        UnsupportedFormatError: When the format cannot write.
        ConfigTypeError: When *obj* holds a value the format cannot represent.
    """
    cls = _format_class(format)
    if "dump" not in _formats.capabilities(cls):
        raise UnsupportedFormatError(f"the {cls.name} format cannot write")
    check_format_options(cls, options, "dump")
    from ._hydrate import dehydrate

    return cls().dumps(dehydrate(obj), **options)

Async

anycfg.aload(*sources, format=None, encoding='utf-8', policy=None, into=None, **options) async

:func:~anycfg.load, awaitable, and able to read async sources.

Each source is anything :func:~anycfg.load takes, or an async stream: an async iterable of str/bytes chunks (response.aiter_bytes()) or an object with async def read() (aiofiles, asyncio.StreamReader). An async source is read whole first, stopping past max_bytes; its format comes from format or its name. Several sources merge as in :func:~anycfg.load, and default= is passed through.

Source code in src/anycfg/_async.py
151
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
async def aload(
    *sources: _ty.Any,
    format: _ty.Any = None,
    encoding: str = "utf-8",
    policy: "_ty.Optional[Policy]" = None,
    into: _ty.Any = None,
    **options: _ty.Any,
) -> _ty.Any:
    """:func:`~anycfg.load`, awaitable, and able to read async sources.

    Each source is anything :func:`~anycfg.load` takes, or an async stream: an
    async iterable of `str`/`bytes` chunks (``response.aiter_bytes()``) or an
    object with ``async def read()`` (aiofiles, `asyncio.StreamReader`). An
    async source is read whole first, stopping past ``max_bytes``; its format
    comes from *format* or its ``name``. Several sources merge as in
    :func:`~anycfg.load`, and ``default=`` is passed through.
    """
    from ._api import _check_location, load

    for source in sources:
        if source is not None:
            _check_location(source, "aload", allow_async=True)
    prepared = await _prepare(sources, _session(policy, encoding))
    return await asyncio.to_thread(
        load,
        *prepared,
        format=format,
        encoding=encoding,
        policy=policy,
        into=into,
        **options,
    )

anycfg.aload_all(source, /, *, format=None, encoding='utf-8', policy=None, into=None, **options) async

:func:~anycfg.load_all as an async iterator; sources as :func:aload.

Source code in src/anycfg/_async.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
async def aload_all(
    source: _ty.Any,
    /,
    *,
    format: _ty.Any = None,
    encoding: str = "utf-8",
    policy: "_ty.Optional[Policy]" = None,
    into: _ty.Any = None,
    **options: _ty.Any,
) -> _ty.AsyncIterator[_ty.Any]:
    """:func:`~anycfg.load_all` as an async iterator; sources as :func:`aload`."""
    from ._api import _check_location, load_all

    _check_location(source, "aload_all", allow_async=True)
    (prepared,) = await _prepare((source,), _session(policy, encoding))
    documents = await asyncio.to_thread(
        load_all,
        prepared,
        format=format,
        encoding=encoding,
        policy=policy,
        into=into,
        **options,
    )
    async for document in _drain(documents):
        yield document

anycfg.adump(obj, target, /, *, format=None, encoding='utf-8', **options) async

:func:~anycfg.dump, awaitable, and able to write an async target.

target is anything :func:~anycfg.dump takes (written in a worker thread), or an async writer: an async def write() (aiofiles; given str, else bytes in encoding) or a writer with write() and async def drain() (asyncio.StreamWriter; given bytes). obj is serialized completely before anything is written.

Source code in src/anycfg/_async.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
async def adump(
    obj: _ty.Any,
    target: _ty.Any,
    /,
    *,
    format: _ty.Any = None,
    encoding: str = "utf-8",
    **options: _ty.Any,
) -> None:
    """:func:`~anycfg.dump`, awaitable, and able to write an async target.

    *target* is anything :func:`~anycfg.dump` takes (written in a worker
    thread), or an async writer: an ``async def write()`` (aiofiles; given `str`,
    else bytes in *encoding*) or a writer with ``write()`` and
    ``async def drain()`` (`asyncio.StreamWriter`; given bytes). *obj* is
    serialized completely before anything is written.
    """
    from . import formats as _formats
    from ._api import _target_name, dump, dumps

    if not _is_async_writer(target):
        await asyncio.to_thread(
            dump, obj, target, format=format, encoding=encoding, **options
        )
        return
    if format is None:
        name = _target_name(target)
        if name is None:
            from .exceptions import UnsupportedFormatError

            raise UnsupportedFormatError(
                f"cannot tell which format to write to {target!r}; pass format="
            )
        format = _formats.for_filename(name)
    text = await asyncio.to_thread(dumps, obj, format=format, **options)
    if not text.endswith("\n"):
        text += "\n"
    drain = getattr(target, "drain", None)
    if inspect.iscoroutinefunction(drain):
        # A StreamWriter: bytes, then wait for the buffer to empty.
        target.write(text.encode(encoding))
        await drain()
        return
    try:
        await target.write(text)
    except TypeError:
        # A binary async file refuses str before writing anything.
        await target.write(text.encode(encoding))

Layers

anycfg.Loader(*, base_dir=None, policy=None, encoding='utf-8', merge='simple', merge_options=None, collect=None, key=None, recursive=False, bound_loops=True, interpolate=False, inject_env=False, wrap=None, on_error=False, format_options=None, formats=None)

Load layered configuration: many sources, merged in order.

Construct one with the settings every call shares, then call :meth:load (merge everything into one result) or :meth:load_all (each document on its own). Each setting can be overridden per call by passing it to either method.

Parameters:

Name Type Description Default
base_dir Any

The directory relative path sources resolve against, and includes inside in-memory content. None means the working directory. A pathapi Path source is never joined to it.

None
policy '_ty.Optional[Policy]'

The :class:~anycfg.Policy; untrusted by default. A list or tuple of Policy mixin classes ([policies.LocalPathsOnly, policies.AllowedSuffixes.using(suffixes={".yaml"})]) is applied to the default policy in that order.

None
encoding str

The codec bytes are decoded with (strict; one BOM ignored), and the default for includes.

'utf-8'
merge '_ty.Union[str, Merge]'

A strategy name ("simple", "substitute", "deep", or one added with :func:~anycfg.register_merge) or a callable (a, b, *, memo=None, **options) -> merged.

'simple'
merge_options '_ty.Optional[_ty.Mapping[str, _ty.Any]]'

Keywords every merge call receives (mergelists).

None
collect '_ty.Optional[str]'

Instead of merging: "last" keeps the last document, "list" collects them in order, "hash" keys them with key.

None
key '_ty.Optional[_ty.Callable[[str, _ty.Any], _ty.Any]]'

(name, document) -> key for collect="hash"; the source's file stem by default.

None
recursive bool

Whether ** in a glob source descends into directories.

False
bound_loops bool

Bound a ** walk through a Windows junction loop.

True
interpolate InterpolateChoice

Render the template text in the merged document's strings, once, after merging: True with Jinja, a tplx engine's name or class ("liquid", "envsubst") with that engine, "vars" with :class:~anycfg.VarsInterpolator (${a.b} references; needs no extra), or an :class:~anycfg.Interpolator object. Engines are sandboxed and strict as the policy says. False (the default) is off.

False
inject_env bool

Give templates and interpolation a read-only env snapshot of the environment.

False
wrap Any

True (or DotAccessibleDict) returns dot-accessible mappings; a callable is applied to the result; None returns plain data.

None
on_error OnError

True skips every failing source (logged at WARNING); a callable (LoadEvent) -> bool decides per failure; False raises.

False
format_options '_ty.Optional[_ty.Mapping[str, _ty.Mapping[str, _ty.Any]]]'

{"json": {...}}: options for a format whenever this loader uses it.

None
formats '_ty.Optional[_ty.Iterable[str]]'

The format names a document may select (an include, a command's #! line). Built-in and code-registered formats by default; a format from an installed plugin must be named here.

None
Source code in src/anycfg/_loader.py
 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
def __init__(
    self,
    *,
    base_dir: _ty.Any = None,
    policy: "_ty.Optional[Policy]" = None,
    encoding: str = "utf-8",
    merge: "_ty.Union[str, Merge]" = "simple",
    merge_options: "_ty.Optional[_ty.Mapping[str, _ty.Any]]" = None,
    collect: "_ty.Optional[str]" = None,
    key: "_ty.Optional[_ty.Callable[[str, _ty.Any], _ty.Any]]" = None,
    recursive: bool = False,
    bound_loops: bool = True,
    interpolate: InterpolateChoice = False,
    inject_env: bool = False,
    wrap: _ty.Any = None,
    on_error: OnError = False,
    format_options: "_ty.Optional[_ty.Mapping[str, _ty.Mapping[str, _ty.Any]]]" = None,
    formats: "_ty.Optional[_ty.Iterable[str]]" = None,
) -> None:
    self._defaults = self._settle(
        dict(
            policy=Policy() if policy is None else policy,
            encoding=encoding,
            merge=merge,
            merge_options=merge_options,
            collect=collect,
            key=key,
            recursive=recursive,
            bound_loops=bound_loops,
            interpolate=interpolate,
            inject_env=inject_env,
            wrap=wrap,
            on_error=on_error,
            format_options=format_options,
            document_formats=(
                None
                if formats is None
                else frozenset(
                    _formats.get(_format_name(name)).name for name in formats
                )
            ),
            base_dir=_as_base_dir(base_dir),
        )
    )

base_dir property

The directory relative sources resolve against, or None.

policy property

The loader's default :class:~anycfg.Policy.

__repr__()

Source code in src/anycfg/_loader.py
1235
1236
def __repr__(self) -> str:
    return f"Loader(base_dir={self.base_dir!r}, policy={self.policy!r})"

aload(*sources, format=None, into=None, default=None, flatten=False, transform=None, **overrides) async

:meth:load, awaitable; a source may also be an async stream.

An async source — an async iterable of str/bytes chunks, or an object with async def read() — is read on the event loop first, stopping past max_bytes; the rest of the load runs in a worker thread.

Source code in src/anycfg/_loader.py
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
async def aload(
    self,
    *sources: _ty.Any,
    format: _ty.Any = None,
    into: _ty.Any = None,
    default: _ty.Any = None,
    flatten: bool = False,
    transform: "_ty.Optional[_ty.Callable[[_ty.Any, str], _ty.Any]]" = None,
    **overrides: _ty.Any,
) -> _ty.Any:
    """:meth:`load`, awaitable; a source may also be an async stream.

    An async source — an async iterable of `str`/`bytes` chunks, or an
    object with ``async def read()`` — is read on the event loop first,
    stopping past ``max_bytes``; the rest of the load runs in a worker
    thread.
    """
    from ._async import loader_aload

    if not sources:
        raise TypeError("aload() needs at least one source")
    return await loader_aload(
        self,
        sources,
        dict(
            format=format,
            into=into,
            default=default,
            flatten=flatten,
            transform=transform,
            **overrides,
        ),
    )

aload_all(*sources, format=None, into=None, transform=None, **overrides)

:meth:load_all as an async iterator; sources as :meth:aload.

Source code in src/anycfg/_loader.py
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
def aload_all(
    self,
    *sources: _ty.Any,
    format: _ty.Any = None,
    into: _ty.Any = None,
    transform: "_ty.Optional[_ty.Callable[[_ty.Any, str], _ty.Any]]" = None,
    **overrides: _ty.Any,
) -> _ty.AsyncIterator[_ty.Any]:
    """:meth:`load_all` as an async iterator; sources as :meth:`aload`."""
    from ._async import loader_aload_all

    if not sources:
        raise TypeError("aload_all() needs at least one source")
    self._call(overrides)  # checked now, not on the first iteration
    return loader_aload_all(
        self,
        sources,
        dict(format=format, into=into, transform=transform, **overrides),
    )

load(*sources, format=None, into=None, default=None, flatten=False, transform=None, **overrides)

Load sources, combine them in order, and return the result.

Parameters:

Name Type Description Default
*sources Any

Paths, glob patterns, cmd:// strings, pathapi paths, file objects, mappings, :class:~anycfg.sources.Source objects, or nested lists of any of these; None and "" are skipped.

()
format Any

Read every source as this format (a name or a :class:~anycfg.formats.Format class), instead of by its file name.

None
into Any

Build this type from the result (see :func:~anycfg.hydrate).

None
default Any

Returned when no source loads — nothing matched, or every one failed under on_error. Never merged with a document.

None
flatten bool

Flatten a mapping of mappings, or a list of lists, by one level; None members are skipped.

False
transform '_ty.Optional[_ty.Callable[[_ty.Any, str], _ty.Any]]'

(document, name) -> document, applied to each document before it is combined.

None
**overrides Any

Any constructor setting, for this call only.

{}

Raises:

Type Description
TypeError

For no sources, or an unknown keyword.

Source code in src/anycfg/_loader.py
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
def load(
    self,
    *sources: _ty.Any,
    format: _ty.Any = None,
    into: _ty.Any = None,
    default: _ty.Any = None,
    flatten: bool = False,
    transform: "_ty.Optional[_ty.Callable[[_ty.Any, str], _ty.Any]]" = None,
    **overrides: _ty.Any,
) -> _ty.Any:
    """Load *sources*, combine them in order, and return the result.

    Args:
        *sources: Paths, glob patterns, ``cmd://`` strings, pathapi paths,
            file objects, mappings, :class:`~anycfg.sources.Source`
            objects, or nested lists of any of these; ``None`` and ``""``
            are skipped.
        format: Read every source as this format (a name or a
            :class:`~anycfg.formats.Format` class), instead of by its
            file name.
        into: Build this type from the result (see
            :func:`~anycfg.hydrate`).
        default: Returned when no source loads — nothing matched, or every
            one failed under ``on_error``. Never merged with a document.
        flatten: Flatten a mapping of mappings, or a list of lists, by one
            level; ``None`` members are skipped.
        transform: ``(document, name) -> document``, applied to each
            document before it is combined.
        **overrides: Any constructor setting, for this call only.

    Raises:
        TypeError: For no sources, or an unknown keyword.
    """
    if not sources:
        raise TypeError("load() needs at least one source")
    call = self._call(overrides)
    session = _Session(call)
    session.keep_text = into is not None
    documents = session.each(
        sources, session.context(), explicit=format, transform=transform
    )
    result, loaded = session.combine(
        documents,
        merge=call.merge,
        merge_options=call.merge_options,
        collect=call.collect,
        default=default,
    )
    if flatten and loaded:
        result = _flatten_result(result)
    return session.finish(result, into=into)

load_all(*sources, format=None, into=None, transform=None, **overrides)

Yield every document of every source, each finished on its own.

For a directory of unrelated files rather than layers of one configuration. A multi-document source (a --- YAML stream) yields each of its documents. Takes the same arguments as :meth:load, except those that only make sense when combining.

Source code in src/anycfg/_loader.py
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
def load_all(
    self,
    *sources: _ty.Any,
    format: _ty.Any = None,
    into: _ty.Any = None,
    transform: "_ty.Optional[_ty.Callable[[_ty.Any, str], _ty.Any]]" = None,
    **overrides: _ty.Any,
) -> _ty.Iterator[_ty.Any]:
    """Yield every document of every source, each finished on its own.

    For a directory of unrelated files rather than layers of one
    configuration. A multi-document source (a ``---`` YAML stream) yields
    each of its documents. Takes the same arguments as :meth:`load`,
    except those that only make sense when combining.
    """
    if not sources:
        raise TypeError("load_all() needs at least one source")
    call = self._call(overrides)
    session = _Session(call)
    session.keep_text = into is not None
    # Checked above, not on the first next(): the documents are a generator.
    return self._documents(session, sources, format, into, transform)

anycfg.LoadEvent

Bases: NamedTuple

One failure offered to on_error.

Attributes:

Name Type Description
error BaseException

The exception, unchanged.

phase str

"load" (reading or parsing path failed), "include" (a source path includes failed), "merge", "interpolate" or "glob" (a directory could not be listed while expanding a pattern; path is that directory).

path Any

The source, or None where no single source applies.

key '_ty.Optional[tuple]'

For "interpolate", the key path of the failing value.

error instance-attribute

key = None class-attribute instance-attribute

path = None class-attribute instance-attribute

phase instance-attribute

Standard locations

anycfg.user_config_path(name=None)

The current user's configuration directory, or name inside it.

%APPDATA% on Windows (~/AppData/Roaming when it is unset); $XDG_CONFIG_HOME or ~/.config elsewhere, macOS included. A relative $XDG_CONFIG_HOME is ignored, as the XDG Base Directory specification requires.

name is joined as :mod:pathlib joins, so it may hold subdirectories ("app/config.toml")::

anycfg.user_config_path("app.toml")    # ~/.config/app.toml
Source code in src/anycfg/_locations.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def user_config_path(name: Optional[Union[str, os.PathLike]] = None) -> pathlib.Path:
    """The current user's configuration directory, or ``name`` inside it.

    ``%APPDATA%`` on Windows (``~/AppData/Roaming`` when it is unset);
    ``$XDG_CONFIG_HOME`` or ``~/.config`` elsewhere, macOS included. A relative
    ``$XDG_CONFIG_HOME`` is ignored, as the XDG Base Directory specification
    requires.

    ``name`` is joined as :mod:`pathlib` joins, so it may hold subdirectories
    (``"app/config.toml"``)::

        anycfg.user_config_path("app.toml")    # ~/.config/app.toml
    """
    if sys.platform == "win32":
        appdata = os.environ.get("APPDATA")
        base = (
            pathlib.Path(appdata)
            if appdata
            else pathlib.Path.home() / "AppData" / "Roaming"
        )
    else:
        xdg = os.environ.get("XDG_CONFIG_HOME")
        base = (
            pathlib.Path(xdg)
            if xdg and os.path.isabs(xdg)
            else pathlib.Path.home() / ".config"
        )
    return base / name if name else base

anycfg.system_config_path(name=None)

The machine-wide configuration directory, or name inside it.

%PROGRAMDATA% on Windows (C:\ProgramData when it is unset); /etc elsewhere. $XDG_CONFIG_DIRS is not consulted: system configuration conventionally lives under /etc.

:func:default_configs layers it under the user's file.

Source code in src/anycfg/_locations.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def system_config_path(name: Optional[Union[str, os.PathLike]] = None) -> pathlib.Path:
    """The machine-wide configuration directory, or ``name`` inside it.

    ``%PROGRAMDATA%`` on Windows (``C:\\ProgramData`` when it is unset);
    ``/etc`` elsewhere. ``$XDG_CONFIG_DIRS`` is not consulted: system
    configuration conventionally lives under ``/etc``.

    :func:`default_configs` layers it under the user's file.
    """
    if sys.platform == "win32":
        base = pathlib.Path(os.environ.get("PROGRAMDATA") or "C:\\ProgramData")
    else:
        base = pathlib.Path("/etc")
    return base / name if name else base

anycfg.default_configs(name)

The system and user files called name, as layers for :func:~anycfg.load.

[system_config_path(name), user_config_path(name)], in that order so the user's file overrides, with None in place of a file that does not exist — the loader skips None, so the list goes straight in::

config = anycfg.load(*anycfg.default_configs("app.toml"), default={})

With neither file present the load returns its default. name is a file name (subdirectories allowed), not a glob pattern.

Source code in src/anycfg/_locations.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def default_configs(name: Union[str, os.PathLike]) -> List[Optional[pathlib.Path]]:
    """The system and user files called ``name``, as layers for :func:`~anycfg.load`.

    ``[system_config_path(name), user_config_path(name)]``, in that order so
    the user's file overrides, with ``None`` in place of a file that does not
    exist — the loader skips ``None``, so the list goes straight in::

        config = anycfg.load(*anycfg.default_configs("app.toml"), default={})

    With neither file present the load returns its ``default``. ``name`` is a
    file name (subdirectories allowed), not a glob pattern.
    """
    if not name:
        raise ValueError("default_configs() needs a file name")
    return [
        path if path.is_file() else None
        for path in (system_config_path(name), user_config_path(name))
    ]