Changelog
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
0.11.2 - 2026-08-16
Changed
- Bounded the
pathlib-nextdependency to>=0.9.0,<0.10(previously unversioned). It carried no bounds at all, so a resolver was free to install a release predating thePathname/MemPathsurface thatbackends/andutils/source.pyimport, or a future minor that changes it. Nothing in the package uses API added after 0.9.0, so the floor sits at the start of the series rather than at its newest patch.
0.11.1 - 2026-08-16
Fixed
sandbox=Trueno longer breaks bare{{ expr }}values. The type-preserving expression path captured its result with_meta.__setitem__(...), an underscore attribute access that Jinja2'sSandboxedEnvironmentrefuses — so every pure-expression config value raisedSecurityErrorunder the 0.11.0sandboxcontrol, while mixed-text templates ("hello {{ name }}") worked. The capture is now a plain callable bound as a render name; SSTI payloads still raiseSecurityErroras before.- The
recursiveoption now actually recurses.ConfigLoader(recursive=True)and the per-callload(recursive=...)override were documented but never forwarded toparse_sources, making both silent no-ops;load_all()now honors the instance setting too. Default (False) glob behavior is unchanged. - Deep merge with
mergelists=Trueno longer drops non-overlapping dicts. A positionally-matched dict from the right-hand list that shared no key with its counterpart was removed from the pending set before the overlap check, so it was neither merged nor appended —Deep([{"k": 1}], [{"z": 9}], mergelists=True)returned[{'k': 1}]instead of[{'k': 1}, {'z': 9}]. Silent data loss. typed_mergenow actually reads parametrized-generic arguments. Three defects shared one root cause — type args were taken from the originalclsinstead of the union-unwrapped origin, and the sequence branch tested a class object with instance checks so it could never run.Dict[str, int]coerced nothing ({"a": "1"}stayed a string),List[str]returned its elements uncoerced, andOptional[Dict[str, int]]pickedNoneTypeas the value type and raisedTypeError: NoneType takes no arguments. Unparametrizeddict/listhints andstr/bytesare unaffected.- The Jinja2 backend no longer crashes when
pathlib_nextis absent. Its ImportError fallback setMemPath = Noneandload()called it anyway, so every.j2/.jinja2source raisedTypeError: 'NoneType' object is not callable— despite the class docstring promising "a real temp file whenpathlib_nextis unavailable". It now materializes a tracked temp file, reusing the mechanismutils/source.pyalready used for in-memory sources, and keeps the rendered basename so backend auto-detection still resolvessettings.yaml.j2to the YAML backend.
0.11.0 - 2026-07-20
Changed
- Narrowed
typed_merge'sget_type_hintsfallback to(TypeError, NameError, AttributeError); the loader/command broad error boundaries that feed theignore_errorpredicate now log the swallowed error at DEBUG and are documented as deliberate. - Faster interpolation. A string with no Jinja delimiter (
{{/{%/{#) — the common case for config values — now returns unchanged without a cache lookup orTemplate.render. The template/expression caches also switched from clear-everything-at-capacity to LRU eviction, and their env keys are weakref-guarded so a recycledid(env)can't return a render bound to a dead environment.parse_sourcesduplicate detection is O(1) per source (set) instead of O(n²) (list).
Added
allow_commandscontrol.ConfigLoader(allow_commands=False)(or perload()call) refuses to execute a command source —cmd://,exec://,sh://,*+fmt://, script-extension files, and any reached via!include— raisingCommandsDisabledErrorinstead. Use it when loading configuration you do not fully trust. Defaults to permissive (commands allowed).sandboxcontrol.ConfigLoader(sandbox=True)runs interpolation in Jinja2'sSandboxedEnvironment, blocking attribute-traversal (SSTI) attacks from untrusted config values. Defaults off.- New
docs/guide/security.mddocumenting the trust model and both controls.
Fixed
!includenow routes through the loader driving the current parse. The include constructor is registered once per YAML loader class, capturing the firstConfigLoader; nested!include/!loadpreviously inherited that first loader's settings (base_dir, merge, and — critically —allow_commands), leaking state across loaders. Includes now use the loader actually performing the load.ConfigLoader.load(merge_options=...)no longer permanently overwrites the instance'smerge_options— the documented per-call override was leaking into every subsequentload().- Backend resolution is now deterministic: recursive backend discovery returns a
definition-ordered list instead of a
set, so when two backends' patterns both match a path, the first-registered one reliably wins (previously hash-order dependent). - Constructing a
ConfigLoader(including the import-time default instance) no longer callssetLevel()on the module logger — library code no longer mutates global logging state. - Stream and unnamed in-memory sources each get a unique virtual path; previously
every stream materialized to the same
MemPath("stream"), so resolving sources up front left all of them holding the last stream's content. CommandBackenddecodes command output as UTF-8 (or an explicitencoding=) instead of the locale codec, which mangled UTF-8 output on Windows; temp-file fallback sources are cleaned up at exit and their suffixes sanitized.
0.10.0 - 2026-07-18
Added
OpaqueMergemixin,opaqueclass decorator, andTypedNamespacebase for customizingtyped_merge, all exported from the package root.OpaqueMerge/opaquemark a type opaque (last object wins, no field introspection — for a fully-built config object or one with factory-function field hints);TypedNamespaceapplies_parse_<field>coercers at construction. The__merge__and_parse_<field>extension hooks are now documented in the merging guide.
Changed
- YAML
!include/!loadauto-registration now logs aWARNINGwhen it overrides a constructor already registered on the loader class, so a redundant manualyaml.add_constructor("!include", ...)is visible rather than silently replaced.
0.9.8 - 2026-07-18
Added
typed_mergeis now exported from the top-levelyaconfiglibpackage (from yaconfiglib import typed_merge), alongsideMergeMethod/ConfigLoaderMergeMethod. It was previously only reachable via the internalyaconfiglib.utils.mergepath.
Fixed
typed_mergeno longer raisesTypeError: issubclass() arg 1 must be a classwhen a field's resolved type hint is a non-class callable (e.g. anipaddress-style factory function such asnetutils.IPNetwork). Such a hint is now treated as an opaque coercer: the last value wins, coerced through the callable when it accepts the value, otherwise returned unchanged. Class hints are unaffected.
0.9.7 - 2026-07-14
Documentation
- Added docstrings across
ConfigBackendand every built-in backend (YAML, TOML, JSON, INI, dotenv, env, command, python, jinja2), and filled inConfigLoader's constructor,load,load_all, andparse_sources. - Rebuilt the docs site:
docs/index.mdis now a full landing page instead of a README redirect stub; added task-oriented guide pages (backends,merging,templating,includes,models) underdocs/guide/; split the flat API reference into per-module pages (api/loader.md,api/backends.md,api/utils.md).
0.9.6 - 2026-07-14
Added
- Added optional nested environment variable loading and scalar coercion to
EnvVarBackendvianested_delimiterandcoerce. - Expanded benchmarks with focused suites for source parsing, merge behavior, Jinja/load interpolation, dot-access lookup, and env backend loading.
Changed
- Optimized
DotAccessibleDict.get()dotted traversal to avoid exception-converting attribute lookup on hot paths.
Fixed
- Fixed
.envparsing so whitespace-delimited inline comments are stripped while hashes inside quoted or unquoted values are preserved. - Defined the missing
ConfigLoader.load_astype variable so runtime type-hint introspection succeeds. - Fixed
Jinja2ConfigLoadersoenvironment=is honored, the backend is discoverable asjinja2, and rendered in-memory configs are parsed without rejoining the parent base directory. - Fixed
ConfigLoader.load()somerge_optionsare passed to merge implementations, includingmergelists=True. - Added an explicit
TypeErrorforflatten=Trueon scalar merged results instead of leaving an unbound local failure path. - Fixed duplicate path tracking, nested iterable option propagation, and stdlib
base_dirglob fallback behavior inparse_sources().
0.9.5 - 2026-07-13
Changed
- Documentation: Replaced AGENTS-specific development and release references in
README.mdwith concrete local install/test commands and a concise release workflow summary for contributors.
0.9.4 - 2026-07-11
Changed
- Performance: Optimized
utils/source.pyby precompiling command matching regex at the module level and avoiding path splitting allocations inhas_glob_pattern(yielding a ~58% speedup in glob checks). - Performance: Optimized
loader.pyandutils/jinja2.pyby caching compiled templates/expressions and reusing the Jinja Environment inload_all(up to 30x faster template interpolation and 8x fasterload_allsequential loads). - Performance: Optimized
utils/merge.pyby fast-pathing standard collection types inis_arrayand bypassing positional dictionary comprehensions in_deep_listswhen positional merging is disabled (yielding a ~19-35% speedup in deep merges).
Fixed
- Fixed
UnboundLocalErrorinsideload_allerror handling path when a load failure occurred before the local variablevaluewas bound.
0.9.3 - 2026-07-11
Fixed
- Fixed false-positive glob pattern detection for
cmd://andexec://sources whose command arguments contained glob metacharacters (e.g.[,]). Paths identified as command sources now bypass the glob check and are yielded directly.
0.9.2 - 2026-07-11
Fixed
- Replaced Python 3.10+
match/casestatement inloader.pywithif/eliffor Python 3.9 compatibility. - Replaced runtime
|pipe union syntax inutils/source.py(SourceLike) withtyping.Unionfor Python 3.9 compatibility.
0.9.1 - 2026-07-11
Fixed
- Corrected dependency declaration from
pathlib_nextto canonical PyPI namepathlib-next.
0.9.0 - 2026-07-11
Added
- Pluggable backend registry with custom backends:
DotenvBackend(parsing.envfiles),EnvVarBackend(queryingos.environwith prefix filtering), andPythonBackend(direct dict injection). - New
CommandBackendsupporting command and shell script execution (cmd://,exec://,.sh,.bat, etc.) with format overrides and shebang-based routing (#!json/#!yaml). - Top-level standard library API parity (
load,loads,dump,dumps) with the newly implementeddumpfunction. - Optional Pydantic model validation (
load_as) with fallback support to dataclasses. - Dot-notation dictionary access wrapper (
DotAccessibleDict) for deep configuration values. - Jinja2 environment features: environment auto-injection (
env.KEY) and strict interpolation mode (strict=True). - Comprehensive unit tests in
tests/test_v2_features.py.
Changed
- API Modernization: Renamed
configloaderargument toloaderacross the public API surface (load,loads, and backends). - YAML Inclusion: Idempotent auto-registration of
!includeand!loadconstructor tags in SafeLoader, now dynamically delegating to the parentConfigLoaderinstance to support nested TOML/JSON/command loads. - Compatibility: Replaced Python 3.10+ specific features to support Python 3.9+, and updated pyproject.toml / CI workflows.
- pathlib_next: Made
pathlib_nextan optional dependency with standard library pathlib, tempfile, and glob fallback behaviors. - Code Organization: Decoupled
typed_mergelogic into its ownutils/typing_merge.pymodule. - License: Changed the license from GNU GPLv3 to MIT License.
- Refactoring: Completely rewrote
utils/merge.pyandutils/jinja2.pyin a clean-room implementation, removing all legacy code dependencies (GPL3 taint fromhiyapycoandyamlinclude). - Error Handling: Improved error handling across
loader.pyandutils/source.py, replacing bareexcept Exception:blocks with targeted exception capturing. - Logging: Adopted idiomatic module-level loggers instead of passing logger instances down the call stack.
- Typing: Adopted modern PEP 604 union types and improved static analysis compatibility in merge functions.
Removed
- Legacy references and code blocks tied to
hiyapyco.