Skip to main content

nonfig

CI codecov

Automatic Pydantic config generation from class and function signatures.

nonfig turns any class or function into a configurable, serializable, and validated component. By generating Pydantic models directly from your code's signatures, it eliminates boilerplate while enforcing type safety and validation.

Quick Example

from nonfig import configurable, DEFAULT, Hyper, Ge


@configurable
class Optimizer:
  def __init__(self, lr: Hyper[float, Ge(0)] = 0.01, momentum: float = 0.9):
    self.lr = lr
    self.momentum = momentum


@configurable
class Model:
  def __init__(
    self,
    hidden_size: int = 128,
    optimizer: Optimizer = DEFAULT,  # Automatically transforms to Optimizer.Config
  ):
    self.hidden_size = hidden_size
    self.optimizer = optimizer


# Instantiate via Config
config = Model.Config(hidden_size=256, optimizer={"lr": 0.001})
model = config.make()  # Returns a Model instance with a real Optimizer

Installation

pip install nonfig
# Optional YAML support:
pip install nonfig[yaml]

Core Concepts

Configuring Classes (Greedy)

When you apply @configurable to a class, nonfig captures all parameters from your __init__ method (or dataclass fields) and generates a .Config class.

@configurable
class Model:
  def __init__(self, layers: int = 3, dropout: float = 0.1):
    self.layers = layers
    self.dropout = dropout


# Direct instantiation still works as usual
m = Model(layers=5)

# Config provides validation and serialization
config = Model.Config(layers=10)
m = config.make()  # Returns Model instance

Configuring Functions (Selective)

For functions, @configurable is selective. It only extracts parameters marked with Hyper[T], DEFAULT, or a Config object. Other parameters are treated as "runtime arguments" that must be passed when calling the configured function.

from nonfig import configurable, Hyper, Gt


@configurable
def train(
  dataset,  # Runtime argument (not in Config)
  *,
  epochs: Hyper[int, Gt(0)] = 10,  # Hyperparameter
  lr: Hyper[float] = 0.01,  # Hyperparameter
):
  print(f"Training for {epochs} epochs with lr={lr}")


# Create a configured version of the function
trainer = train.Config(epochs=20).make()

# Call it with runtime arguments
trainer(my_dataset)  # Uses epochs=20, lr=0.01

Nested Configuration & DEFAULT

The power of nonfig lies in its ability to compose configurations. Any @configurable class used as a type hint is automatically transformed into a nested configuration. Use DEFAULT to automatically instantiate the nested config with its own defaults.

@configurable
class Trainer:
  def __init__(self, optimizer: Optimizer = DEFAULT):
    self.optimizer = optimizer


# 'optimizer' can be passed as a dict, an Optimizer.Config, or an Optimizer instance
config = Trainer.Config(optimizer={"lr": 0.0001})
trainer = config.make()
assert isinstance(trainer.optimizer, Optimizer)

Validation & Constraints

Use Hyper[T, ...] to attach Pydantic-style constraints to your parameters. These are enforced whenever a .Config is created.

from nonfig import configurable, Hyper, Ge, Le, Gt, MinLen, Pattern


@configurable
class Network:
  def __init__(
    self,
    lr: Hyper[float, Gt(0)] = 0.01,
    dropout: Hyper[float, Ge(0), Le(1)] = 0.5,
    name: Hyper[str, MinLen(3), Pattern(r"^[A-Z]")] = "Net",
  ): ...

Available constraints: Ge (>=), Gt (>), Le (<=), Lt (<), MinLen, MaxLen, MultipleOf, Pattern.


Features

Inheritance & Smart Propagation

nonfig supports automatic inheritance. If a subclass of a @configurable class includes **kwargs in its __init__, it automatically inherits all configurable parameters from its parents.

@configurable
class Base:
  def __init__(self, x: int = 1):
    self.x = x


class Sub(Base):
  def __init__(self, y: int = 2, **kwargs):
    super().__init__(**kwargs)
    self.y = y


# Sub.Config now has both 'x' and 'y'
config = Sub.Config(x=10, y=20)

External Components (wrap_external)

Make third-party classes (e.g., from PyTorch or Scikit-learn) configurable without modifying their code. wrap_external is always greedy, capturing every parameter in the signature.

from torch.optim import Adam
from nonfig import wrap_external

AdamConfig = wrap_external(Adam, overrides={"lr": Hyper[float, Gt(0)]})


@configurable
class Experiment:
  def __init__(self, opt: AdamConfig = DEFAULT):
    self.opt = opt

Function Type Proxies (.Type)

When a @configurable function is used as a nested dependency, use .Type for the type hint to enable proper configuration nesting and IDE support.

@configurable
def preprocessor(data): ...


@configurable
class Pipeline:
  """A pipeline with a nested configurable preprocessor."""

  def __init__(self, prep: preprocessor.Type = DEFAULT):
    self.prep = prep


### Overriding Nested Defaults

You can override default parameters of nested configurations directly within the type annotation using the `Overrides` utility. This is particularly useful for setting different defaults for the same configurable component used in different contexts.

**Validation:** Since `nonfig` uses `extra="forbid"`, all overrides (including those in constant dictionaries) are strictly validated against the target's signature. Misspelled keys will raise a `ValidationError` at decoration time.

```python
from nonfig import Overrides


@configurable
class Strategy:
  def __init__(
    self,
    # Sets default window to 100 specifically for the long MA
    long_ma: Overrides[moving_average.Type, "window": 100] = DEFAULT,
    # Sets default window to 3 specifically for the short MA
    short_ma: Overrides[moving_average.Type, "window": 3] = DEFAULT,
  ):
    self.long_ma = long_ma
    self.short_ma = short_ma


You can also provide a full dictionary of overrides directly:

```python
D = {"window": 100}


@configurable
class Strategy:
  # Injects the entire dictionary D as overrides for the moving average component
  long_ma: Overrides[moving_average.Type, D] = DEFAULT

Overrides also supports multiple arguments, merging dictionaries, slice syntax, and even nested dictionaries to reach deeper into the configuration tree:

@configurable
class ComplexStrategy:
  def __init__(
    self,
    # Merges dictionary, slice syntax, and nested dictionary
    ma: Overrides[
      nested_ma.Type, {"other": 10}, "window":50, "sub_component" : {"param": 1.0}
    ] = DEFAULT,
  ):
    self.ma = ma

Leaf Markers

Use Leaf[T] to disable "Config magic" for a specific parameter. This forces the field to accept only raw instances of the class, preventing nonfig from transforming it into a nested configuration.

from nonfig import Leaf


@configurable
class Processor:
  def __init__(self, model: Leaf[MyModel]):
    # 'model' must be a MyModel instance.
    # Passing a dict or MyModel.Config will raise a ValidationError.
    self.model = model

Leaf vs. DEFAULT:

  • DEFAULT is for Composition: It says "This is a sub-component; please auto-configure it using its own defaults."
  • Leaf[T] is for Inversion of Control: It says "This is an external dependency that I (the user) will provide as a pre-instantiated object."

You should not use Leaf[T] = DEFAULT. They represent opposite intents: Leaf blocks the configuration transformation that DEFAULT specifically requests. This is now enforced at runtime with a TypeError.

JAX, Flax & jaxtyping

nonfig is designed for machine learning research:

  • jaxtyping: Preserves dimension metadata and works with @jaxtyped.
  • Flax: Supports nn.Module classes natively.
  • Order: Always place @configurable as the outermost decorator.

Tooling & CLI

CLI Overrides (run_cli)

Easily run your experiments with command-line overrides using dot-notation.

from nonfig import run_cli

if __name__ == "__main__":
  # python train.py model.layers=5 optimizer.lr=0.001
  result = run_cli(train)

IDE Support (nonfig-stubgen)

Because nonfig generates classes dynamically, IDEs might need help with autocompletion. Generate .pyi stubs for your entire project:

nonfig-stubgen src/

Config Loaders

Support for JSON, TOML, and YAML (requires pyyaml):

from nonfig import load_yaml

config_data = load_yaml("config.yaml")
config = Model.Config(**config_data)

Performance

nonfig is optimized for high-performance ML loops. Config.make() uses a cached factory that is 70-80% faster than a standard Pydantic model_validate.

Pattern Latency Note
Raw Class() ~0.15µs Baseline
Config.make() (Class) ~0.29µs Reused config instance
Config(...).make() ~1.85µs Full lifecycle

Best Practice: Call .make() once outside your training loop.


Comparison

Feature nonfig gin-config hydra tyro
Philosophy Code-first DI YAML-first CLI-first
Validation Pydantic None Runtime Parse-time
Nesting Automatic Manual Manual Automatic
IDE Support Stubs (.pyi) None Partial Full

License

MIT License

Metadata

Release files for nonfig 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nonfig 0.1.2
File Size Uploaded
nonfig-0.1.2.tar.gz 120.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nonfig 0.1.2
File Interpreter ABI Platform
nonfig-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 170.2 kB

Release files / nonfig-0.1.2.tar.gz

Download URL nonfig-0.1.2.tar.gz
Size 120.0 kB
Tags Source
SHA-256 checksum
How to use checksums
eafe498e0773c28d37cecda0f23d09c4be8c454351b174d53fc630db5719243c
BLAKE2b-256 checksum
How to use checksums
c02f79a55eae621a227148ffcc19f7d73cccd5ce7bd9da2fab402efa7cc33749
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 18, 2026.

Transparency log

Release files / nonfig-0.1.2-py3-none-any.whl

Download URL nonfig-0.1.2-py3-none-any.whl
Size 50.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5156175cfe00be6e25fa6fc307502cd1617a5a5f9dd01ccf66be5c95775a8c07
BLAKE2b-256 checksum
How to use checksums
97b47f0791b17fdabd6ac0897a7b8e6a96f9683f4599479d4dada26718892b07
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page