Changelog¶
cfx 0.7.0 (2026-04-23)¶
Features¶
Added
is_set(),unset(), andreset()public API toConfigField.is_set(obj)returnsTruewhen the field has an explicit value stored on the instance (as opposed to using its callable default or env-var fallback).unset(obj)removes the stored value, reverting to the default.reset(obj, value=None)either unsets or sets a new value through the full normalize→validate pipeline.Added
normalize()/validate()separation toConfigField.normalize()runs first (idempotent coercion to canonical form) andvalidate()runs after. Built-in types that previously coerced in__set__or__init__now implementnormalize():MultiOptions(list/tuple → set),Path(str →pathlib.Path),Range(list → tuple),List(tuple → list),Date/Time/DateTime(ISO string → datetime object).Added field-owned CLI:
to_argparse_kwargs()andto_click_option()methods onConfigFieldreplace the monolithic dispatch tables incli.py. Each field type owns its argparse/click representation.BoolusesBooleanOptionalActionand click flag pairs;Optionsaddschoices;MultiOptionsusesnargs="+"andmultiple=True;Seed,Range,Path,Dict,Date,Time,DateTimeeach supply an appropriatemetavar.Added field-owned serialization:
serialize()anddeserialize()methods onConfigFieldreplace the monolithicConfig._serialize_value(). Each field type encodes its own on-disk form.Pathserializes as a string,MultiOptionsas a sorted list,Rangeas a list,Date/Time/DateTimeas ISO strings. The base implementation is an identity pass-through.Added runnable examples (
examples/), rewrotecustom-fieldsdocs around thenormalize/validatepattern, addedis_set/unset/resetandvalidate-after-updatesections to the using guide, fixed sharp-edges page, removed TOML references throughout, and wired up--doctest-modulesso all inline doctests run as part of the test suite.Rewrote fields and field-modifiers intro sections for clarity: tradeoff-oriented framing for declaration styles and modifiers, fixed all broken RST cross-references, added
transient=modifier documentation, moved modifier rough-edges content from fields.rst to field-modifiers.rst where it belongs. Also fixed remaining TOML references in cli.rst, capitalised normalize/validate section heading in custom-fields.rst, added idempotency link to sharp-edges, clarified thatvalidate()is on the Config class in advanced.rst, and wired doctest runs into CI.
Bug Fixes¶
Fixed
Config.validate()not being called afterConfig.update(). Cross-field invariants defined invalidate()are now enforced on programmatic updates, not only on deserialization.Fixed a bug where using
components=on a subclass silently discarded all fields inherited from the parent class. AConfigsubclass that both inherits from a parent and declarescomponents=[...]now correctly includes all inherited fields alongside the new nested sub-configs.Path fields now serialize using forward slashes on all platforms. Previously,
str(pathlib.Path(...))produced backslashes on Windows, breaking cross-platform round-trips when a config was saved on Windows and loaded on Linux or macOS.
Removals¶
Removed TOML serialization support (
from_toml(),to_toml(), and thetomloptional dependency). TOML cannot representNonenatively, causing silent data loss forSeed(None)and similar fields on round-trip. Use YAML for serialization instead.
cfx 0.6.0 (2026-04-21)¶
Features¶
Schema versioning: declare
_version = <int>on aConfigclass to embed the schema version into_dict()output.from_dict()emits aUserWarningwhen the stored version differs from the class version, and ignores the_versionkey during field assignment (including understrict=True).Field(): annotation-native field factory. Declare fields with a type annotation andField()as the right-hand side — the concreteConfigFieldsubclass is inferred at class-definition time::from cfx import Config, Field from typing import Literal class SearchConfig(Config): n_sigma: float = Field(5.0, "Detection threshold", minval=0.0) method: Literal["DBSCAN", "RANSAC"] = Field("DBSCAN", "Algorithm") verbose: bool = Field(False, "Enable verbose output")Supported annotations:
bool,int,float,str,pathlib.Path,Literal[...]→Options,set[Literal[...]]→MultiOptions,list[T]→List,tuple[T, T]→Range,int | None→Seed,int | float→Scalar,datetime.date/time/datetime,dict,typing.Any,Final[T](impliesstatic=True). Callable defaults and all constraint kwargs (minval=,env=,static=,transient=, etc.) pass through to the resolved field type. Explicit field types remain fully supported and can coexist withField()on the same class.
Documentation¶
docs/refactored to split out Fields into 7 separate sections. AddFields,Field ModifiersandCustom Fielddocuments, expand the subsection inFieldsto cover the type inferred and explicit fields. (https://github.com/DinoBektesevic/cfx/issues/docs_refactor)docs/fields.rstrewritten:Field()and annotation syntax are the primary declaration style, with explicit types documented as an escape hatch for custom validation and domain-specific fields. Annotation → field type mapping table added.docs/index.rst,docs/defining.rst, andREADME.mdupdated to useField()throughout.
Internal¶
Field-related code reorganized into a
src/cfx/types/sub-package:config_field.py(ConfigFieldbase),types.py(concrete field types,Alias,Mirror), andtyped_field.py(Field,FieldSpec,resolve_field_spec).types/__init__.pyre-exports the full public surface — all existingfrom cfx import ...imports are unaffected. Shared dotpath utilities (walk,walk_set,strip_none) extracted intosrc/cfx/utils.py.AliasandMirrormoved fromviews.pyintotypes/types.pyalongside the other field descriptor classes.src/cfx/__init__.pyrewritten with explicit re-exports; no bare wildcard imports.
cfx 0.5.0 (2026-04-19)¶
Features¶
AliasedView: a self-contained view that auto-generates prefixedAliasdescriptors for every field in each declared component. Passcomponents=[...]and optionallyaliases=[...]to override the prefix per component (Nonegives a flat, unprefixed name). Name conflicts raiseValueErrorat class-definition time.FlatView: anAliasedViewwith all prefixes set toNone— every component field is exposed directly by name. Raises on conflicts.Mirror: a config descriptor that keeps two or more dotpaths in sync. Declaringshared = Mirror("a.x", "b.x")on aConfigfans writes to every path and asserts agreement on read, raisingValueErrorwith a diff if the paths disagree. AcceptsFieldRefobjects or plain dotpath strings.FieldRef: accessing a field or component on aConfigclass (rather than an instance) now returns aFieldRefpath proxy instead of the raw descriptor.FieldRefobjects chain attribute access to build validated dotpath strings and are the preferred input toAliasandMirror— passAlias(SomeConfig.component.field)instead ofAlias("component.field")to keep paths refactorable via IDE rename and go-to-definition.ComponentRef: each component slot on aConfigclass now has a descriptor installed automatically, soSomeConfig.componentreturns aFieldRefthat can be chained (SomeConfig.component.field). Instance access is unchanged.
Internal¶
_ConfigTypemetaclass removed. Field collection and component wiring now happen inConfig.__init_subclass__, which runs at class-definition time for every subclass. Behaviour is identical; the metaclass was an implementation detail.ConfigMetarenamed to_ConfigTypeto signal that it is not part of the public API and will be replaced by__init_subclass__in a future release.Internal
getattr(cls, field_name)call sites inconfig.pyanddisplay.pythat previously relied onConfigField.__get__returning the descriptor on class access have been updated to usecls._fields[name]directly.
cfx 0.4.0 (2026-04-19)¶
Removals¶
method="unroll"composition mode removed. Defining a class withmethod="unroll"now raisesTypeErrorat class-definition time. Use nested composition (the default whencomponents=is provided) instead.
Bug Fixes¶
diff()now recurses into nested sub-configs. Differences in nested fields appear with dot-notation keys (e.g."search.n_sigma"). Previously nested changes were silently invisible.update()now accepts nested confid keys: pass adictto update fields within a sub-config, or aConfiginstance to replace it. Previously passing a nested confid raisedKeyError.freeze()now propagates to all nested sub-configs. Previouslyfreeze()only blocked writes on flat fields of the top-level config.freeze()now also prevents replacing a nested sub-config via attribute assignment (e.g.cfg.search = other).CLI:
--field noneforSeed(and any field whosefrom_stringreturnsNone) now correctly appliesNoneas the field value. Previously theNonereturn fromfrom_stringwas mistaken for “flag not supplied” and silently ignored.Nested
components=are now stored in declaration order. Previously the internal dict had components in reverse order, causing sub-configs to appear in reversed order inprint(cfg)and iteration.
cfx 0.3.0 (2026-04-18)¶
Features¶
Added
transientkeyword to all field types. Callable defaults are now skipped during serialization when no explicit value has been stored — the callable reconstructs the value naturally on deserialization. Settransient=Falseto preserve the old snapshot behaviour instead.Config now implements
__iter__, completing the dict-like protocol.for key in cfgworks the same asfor key in cfg.keys().
Bug Fixes¶
Unroll composition (
method="unroll") now raisesValueErrorat class-definition time when two components share a field name. Previously the conflict was silently resolved by ordering. Override a component field by redeclaring it directly on the composing class.
Removals¶
Removed the
UserWarningthat fired when serializing a config with callable defaults. The warning fired even for correct usage and gave no actionable guidance. The correct behaviour (skip transient fields; usetransient=Falsefor snapshots) is now the default — see thetransientfeature entry.
Documentation¶
Composition docs updated: unroll field resolution order is now documented explicitly (inherited → components left-to-right → own fields), parallel name conflicts across components are called out with an error example, and the “first component wins” wording that described the old silent-override behaviour has been removed.
Internal¶
Migrated CI linting from flake8 to ruff. Added pre-commit hook running ruff format and ruff check.
cfx 0.2.1 (2026-04-17)¶
Bug Fixes¶
Composing two components with the same
confidnow raisesValueErrorat class-definition time instead of silently dropping the first component.IntandFloatfields now rejectboolvalues. PreviouslyTrue/Falsewere silently accepted as1/0becauseboolis a subclass ofintin Python. This was inconsistent withBool, which explicitly rejects integers.Path.validate()now raisesTypeErrorfor non-Pathvalues instead of silently returning.Path.__init__also now normalizes string defaults topathlib.Pathat field-definition time, consistent with the pattern described for custom fields.cfg["confid"]now returns the nested sub-config object, fixing a broken dict-like contract where"source" in cfgcould beTruebutcfg["source"]raisedKeyError.
Documentation¶
Documentation improvements: added sharp-edges entries for TOML dropping
Nonevalues andList.from_stringsilent JSON fallback; documentedvalidate()call timing; addedOptions/MultiOptionsparameter order note; added “which mode to use” guidance to composition page; added intro sentences to serialization, using, sharp-edges, and CLI pages; added YAML output example and CLI dot-notation mapping table. Landing page title and subtitle styling added viacustom.css.
Internal¶
Added
tests/test_display.pycovering the display module, plus regression tests for the four bug fixes and env-var precedence, nested file loading, and_repr_html_.
cfx 0.2.0 (2026-04-17)¶
Features¶
Improved
config_treeindisplay.py: replacedtextwrap.fillwith unicode box-drawing characters (├─,└─,│). Multiline docstrings are now preserved exactly as written rather than reflowed into a single paragraph. Nesting depth is indicated by a 4-space continuation indent per level. The Config column cap inmake_tablewas raised from 15 to 25 characters so that class names are never broken mid-word.make_tableandas_tablenow accept atable_attrskeyword argument that adds HTML attributes (e.g.class,id) to the generated<table>element, making it straightforward to target the output with custom CSS.
Documentation¶
Switched documentation theme from Alabaster to Furo with light and dark mode logos, GitHub and PyPI footer icons, and sidebar sections (User Guide, API Reference, Development). Updated the canonical example throughout to showcase flat fields alongside deep nested sub-configs. Added a Changelog page. Removed redundant per-page
.. contents::directives that conflicted with Furo’s built-in sidebar navigation.
cfx 0.1.0 (2026-04-17)¶
Features¶
Every
Configsubclass now exposesadd_argumentsandfrom_argparsefor argparse, plusclick_optionsandfrom_clickfor click (optional dependency). Nested sub-configs use dot-notation flags (e.g.--search.n-sigma), including arbitrarily deep nesting (e.g.--middle.inner.x). An optional config-file positional argument is registered automatically — YAML or TOML files are loaded first and CLI flags are applied on top.Nested container configs can now declare their own flat fields alongside sub-configs. Previously, any flat field on a
method="nested"class was silently discarded. Serialization (to_dict,from_dict, YAML, TOML), display (str,repr, HTML), equality,__contains__, and CLI flags all handle the mixed case correctly. Expected dict shape:{"top_field": 99.0, "inner": {"x": 1.0}}.
Internal¶
Added GitHub Actions CI running pytest and flake8 across Python 3.11–3.14 on Ubuntu, Windows, and macOS. Added a monthly canary workflow against Python pre-releases. Docs are built and hosted automatically by Read the Docs on every push to main and on version tags.
Renamed internal
_from_env_strto the publicfrom_stringon allConfigFieldsubclasses. Added symmetricto_stringfor display. The display table now rendersPath,Dict,Date,Time,DateTime, andMultiOptionsvalues cleanly instead of using rawrepr.Renamed internal class attribute
defaultsto_fieldsfor clarity._fieldsholds flatConfigFielddescriptors;_nested_classesholds component classes. The underscore prefix on both makes the symmetry explicit.Type coercions moved into
ConfigField.__set__:Listcoerces tuples to lists,MultiOptionscoerces lists and tuples to sets. This removes ad-hoc coercion fromfrom_argparseandfrom_clickand ensures consistent behaviour regardless of how a value is assigned.