This release is a pre-release and may not be stable for production use.
nshconfig
Typed, Python-first configuration for ML runs, built on Pydantic.
nshconfig adds two ideas to ordinary Pydantic models:
- explicit mutable drafts for assembling incomplete configuration;
- declaration-ordered Python interpolation over canonical validated values.
There is no YAML language, registry, loader, code generator, or provenance layer.
Pydantic's authoring API is re-exported so one import nshconfig as C is enough.
Documentation | Semantic design
Install
pip install --pre nshconfig
pip install --pre 'nshconfig[treescope]' # rich notebook rendering
pip install --pre 'nshconfig[transport]' # trusted cloudpickle transport
pip install --pre 'nshconfig[all]' # both optional features
nshconfig supports Python 3.10 through 3.14 and Pydantic 2.13 through the
latest Pydantic 2.x release.
Two construction modes
Calling a config class has ordinary Pydantic meaning and returns a validated, field-frozen final:
import nshconfig as C
class Child(C.Config):
x: int = 1
y: int = 2
class Parent(C.Config):
child: Child = Child()
final = Parent(child=Child(x=10, y=20))
assert not C.is_draft(final)
Composition uses an explicit draft and one validation boundary:
work = Parent.config_draft()
assert C.is_draft(work.child)
work.child.x = 10
final = work.config_finalize()
assert final == Parent(child=Child(x=10, y=2))
assert C.is_draft(work) # finalization is non-destructive
A normally constructed Config default is a template. When its parent becomes a
draft, default-origin children become fresh drafts recursively through annotated
lists, tuples, mappings, unions, and TypedDict values. An explicitly assigned
final remains a final.
Required fields with one concrete Config annotation lazily create child drafts.
Reading another unset required field raises UnsetError. Draft writes are not
validated until config_finalize().
Interpolation
interp() derives one complete field value. Pydantic field declaration order is
dependency order:
class Norm(C.Config):
dim: int = C.interp(lambda context: context.parent(Model).dim)
class Model(C.Config):
dim: int = 768
norm: Norm = C.Field(default_factory=Norm.config_draft)
assert Model().norm.dim == 768
The callable may use context.current(), parent(), root(), or
nearest(ConfigType). It sees only earlier fields whose complete Pydantic field
validation has finished. The interpolation result then runs through the target
field's normal validation pipeline.
Use C.Field(default_factory=Child.config_draft) when a default child needs its
parent's interpolation context. A direct Child() default must be valid on its
own when the parent class body executes.
Project composition convention
Keep reusable config builders under src/project/configs/ as ordinary in-place
mutators:
def resnet50(cfg: ModelConfig, *, d_model: int = 256) -> ModelConfig:
cfg.d_model = d_model
return cfg
Root files under configs/ use the same contract:
def __config__(cfg: TrainConfig) -> TrainConfig:
resnet50(cfg.model)
cfg.seed = 7
return cfg
The application owns loading. It creates the expected root draft, calls
__config__, verifies that the returned object is the identical draft, and calls
config_finalize() exactly once. nshconfig intentionally provides no loader or
registry.
Pydantic behavior
Pydantic owns fields, aliases, validators, constraints, serialization, JSON
Schema, and normal constructors. nshconfig re-exports Pydantic's non-deprecated
authoring API unchanged, so use C.Field, C.ConfigDict, C.field_validator,
C.TypeAdapter, and the rest from the same namespace. Direct Pydantic imports
remain equivalent.
The base config is strict, forbids extras, validates defaults, revalidates model
instances, uses attribute docstrings as field descriptions, and is shallowly
field-frozen. A project base class may change policy such as strictness, but not
lifecycle settings. Attribute descriptions require inspectable class source;
C.Field(description=...) is the explicit fallback and takes precedence.
Model validators retain native Pydantic semantics. Model-after hooks may mutate
or replace values, which can make an interpolated relationship stale. Likewise,
final.model_copy(update=...) does not validate its updates. To validate the
current concrete contents of a final, use:
checked = type(final).model_validate(final)
Drafts cannot be copied or serialized through Pydantic or JSON. Trusted pickle transport is the explicit exception described below. Finals use value equality and the same field-value hashing rule as frozen Pydantic models: they are hashable exactly when all field values are hashable. Freezing is shallow, so lists, dictionaries, sets, and arbitrary objects retain ordinary Python mutability.
The native lifecycle API is Config, Context, interp, is_draft,
DraftError, and UnsetError, plus __version__; the remaining public names
are Pydantic authoring re-exports.
Trusted executable transport
Cloudpickle can transport notebook-local classes, drafts, and interpolation
callables between compatible trusted environments. Pickle data can execute code;
never load it from an untrusted source. A final contains concrete values and cannot
recreate the original draft recipe. Both normal annotations and
from __future__ import annotations are supported.
See the semantic design for the complete lifecycle and validation contract.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nshconfig-2.2.0a0.tar.gz.
File metadata
- Download URL: nshconfig-2.2.0a0.tar.gz
- Upload date:
- Size: 34.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e23f9682753c5d08c9fc9fd67a2f2ed30b03f6f297c442a27bffc27c6711f7ea
|
|
| MD5 |
e10e8b2b7d35c2d3e39d300d20cac5a3
|
|
| BLAKE2b-256 |
ee4468e906feda9d0868c07d20f60e969c44ec4d31dd2b615026c17fff32bf79
|
File details
Details for the file nshconfig-2.2.0a0-py3-none-any.whl.
File metadata
- Download URL: nshconfig-2.2.0a0-py3-none-any.whl
- Upload date:
- Size: 41.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6e95741ad3d5b384b238808e88facd70a1c8f5b72861de5e50debff56c07514
|
|
| MD5 |
7825f6bcb55019c53406d33e6b79b162
|
|
| BLAKE2b-256 |
57daaf0bc999ab610896835dd72d959dd64d20ded5dedc9f1518b772daeed2d5
|