tunableX
Typed configuration, declared where your Python functions use it.
tunableX turns selected function arguments into Pydantic configuration models,
JSON Schema, JSON/YAML defaults and command-line flags. Compose a configuration
using app tags or static analysis of an entrypoint, then inject it with use_config.
- Keep parameters beside the logic, or share them through
TunableParamsclasses. - Validate types, constraints and unknown settings with Pydantic v2.
- Load JSON, YAML or TOML; explicit CLI flags override file values and defaults.
- Support ordinary functions, methods, static/class methods and async functions.
- Discover calls without running the entrypoint; use tags for dynamic dispatch.
Installation
Python 3.10+ and Pydantic 2.12.5+ (<3) are required.
pip install tunablex # JSON, TOML and standard argparse
pip install 'tunablex[yaml]' # add YAML
pip install 'tunablex[jsonargparse]' # optional parser integration
pip install 'tunablex[all]' # both optional integrations
The base package does not require PyYAML or jsonargparse. On Python 3.10,
tomli is installed automatically for TOML support.
A complete example
Save as train.py:
from argparse import ArgumentParser
from pydantic import Field
from tunablex import tunable, add_flags_by_app, build_cfg_from_file_and_args, use_config
@tunable(namespace="train", apps="training")
def train(epochs: int = Field(10, ge=1, description="Number of training epochs"),
learning_rate: float = Field(0.001, gt=0),
verbose: bool = False):
print(epochs, learning_rate, verbose)
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument("--config", help="JSON, YAML or TOML configuration")
Config = add_flags_by_app(parser, "training")
args = parser.parse_args()
cfg = Config.model_validate(build_cfg_from_file_and_args(Config, args))
with use_config(cfg):
train()
python train.py --train.epochs 25 --train.verbose
python train.py --config train.json --no-train.verbose
python train.py --help
train.json can contain only the values you want to change:
{"train": {"epochs": 20, "learning_rate": 0.01}}
Precedence is function/model defaults → file → explicit CLI flags.
Explicit Python arguments have the final say: train(epochs=3) uses 3 even
inside an active config. A direct train() outside a context resolves declared
Field defaults too; metadata objects are never passed as selected argument values.
Shared parameter declarations
Use Field attributes on a TunableParams subclass. A class name becomes a
snake_case namespace with the Params suffix removed; MainParams and
RootParams represent the root. Nesting/aliases, rather than inheritance,
create nested namespaces. Inheritance reuses fields within the subclass namespace.
from pydantic import Field
from tunablex import TunableParams, make_config_for_app, tunable, use_config
class OptimizerParams(TunableParams):
rate: float = Field(0.01, gt=0, description="Learning rate")
class TrainParams(TunableParams):
epochs: int = Field(10, ge=1)
Optimizer = OptimizerParams
@tunable(apps="training")
def step(epochs=TrainParams.epochs, lr=TrainParams.Optimizer.rate):
return epochs, lr
Config = make_config_for_app("training")
with use_config(Config.model_validate({"train": {"optimizer": {"rate": 0.1}}})):
assert step() == (10, 0.1)
This exposes --train.epochs and --train.optimizer.rate; a local argument can
have a different name (lr). Reusing OptimizerParams under multiple parents
keeps each namespace independent. A string immediately following a field
assignment supplies its description when Field(description=...) is absent.
Composition and schema generation
Import your decorated functions before composing a model.
from tunablex import make_config_for_app, schema_for_app, write_schema
Config = make_config_for_app("training")
schema, defaults = schema_for_app("training")
write_schema("config/train", schema, defaults, yaml=False)
This writes config/train.schema.json and config/train.json. Omit yaml=False
to also write .yml when PyYAML is installed, or set yaml=True to require it.
Required values are omitted from defaults templates and remain required by the
schema. A template with required fields must be completed before use.
You can discover an entrypoint's reachable functions instead of tagging them:
from tunablex import make_config_for_entry, schema_for_entrypoint
from myapp.pipeline import train_main
Config = make_config_for_entry(train_main)
schema, defaults = schema_for_entrypoint(train_main)
Discovery follows direct functions, imported/module aliases, closures, simple local aliases, constructors and statically resolvable methods. It examines both branches and never calls application functions or evaluates properties. It is conservative: for callback tables, factories or runtime plugin selection, use explicit app tags. Imports, annotation evaluation and default factories are ordinary Python execution; schema generation is not an untrusted-code sandbox.
The installed CLI supports the same flows:
tunablex schema --app training --import train --out config/train
tunablex analyze --entry myapp.pipeline:train_main --out config/train
python -m tunablex analyze --entry myapp.pipeline:train_main
Omit --out to print a JSON object containing schema and defaults.
Use --sys-path src for a package that is not yet installed.
CLI values
Both argparse.ArgumentParser and jsonargparse.ArgumentParser are supported.
add_flags_by_entry(parser, entrypoint) selects flags through static discovery.
| Parameter type | Example |
|---|---|
int, float, str, Path |
--train.epochs 20 |
bool |
--train.verbose / --no-train.verbose |
Literal, enum |
--optimizer adam, --level 2 |
list[int], Sequence[int], tuple[int, ...] |
--layers 128 256 |
| Empty collection | --layers with no following values |
| Fixed tuple | --shape '[640,480]' |
| Mapping | --weights '{"main": 0.8}' |
| Nullable value | --seed null |
| Nested model | --section.field value |
Required fields can come from the config file or CLI. Validation happens after merging. Unknown fields are errors, including nested misspellings. Strings are not automatically treated as Python expressions; JSON is used for structured values.
Documentation and examples
- API reference: public functions, types and error behavior.
- Design, limitations and review findings.
- Migration notes and changelog.
- Contributor and release guide.
- Examples: app tags, shared declarations, CLI integration and schemas.
Run examples as modules from a source checkout:
python -m examples.jsonargparse_app.train_jsonarg_app --train.epochs 20
python -m examples.jsonargparse_app.train_jsonarg_params --model.hidden_sizes 128 256
python -m examples.trace_generate_schema --entry train --prefix config/train
python -m examples.argparse_trace.train_trace --config config/train.json
Directories named *_trace retain their historical names; they now use static
analysis. Current API names are TunableParams, make_config_for_app,
schema_for_app, make_config_for_entry and schema_for_entrypoint.
Earlier README names such as TunableParameters and schema_by_trace are not
exported aliases.
Contributors and license
Created by Jacques Papper, with core contributions by Vincent Drouet. See CONTRIBUTORS.md. Contributions are welcome.
Metadata
Release files for tunablex 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tunablex-0.2.0.tar.gz | 40.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tunablex-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 61.8 kB
Release files / tunablex-0.2.0.tar.gz
| Download URL | tunablex-0.2.0.tar.gz |
|---|---|
| Size | 40.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d0bfb5c014c7c7f447cd1be9e3549a6452412494d56d3c70e047f78bcc021c1a
|
|
BLAKE2b-256 checksum How to use checksums |
ffde7e3fe3f547829d9532bb7a9fb5619af1e3af9b29296c839e46382288c755
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 4, 2026.
Transparency logRelease files / tunablex-0.2.0-py3-none-any.whl
| Download URL | tunablex-0.2.0-py3-none-any.whl |
|---|---|
| Size | 21.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
256d26f10fd63c2759bc2c323d78da8617fb3206ac111377d6319f253c52d527
|
|
BLAKE2b-256 checksum How to use checksums |
8e7e2f694588452c799c39da0164b78d549defce3f9224c62bbae3289f18c142
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 4, 2026.
Transparency log