Automatic Pydantic config generation from function signatures with hyperparameters
Project description
hipr
(pronounced "hyper")
Automatic Pydantic config generation from class and function signatures.
Turn any class into a configurable, serializable, validated component—just add @configurable and mark tunable parameters with Hyper[T].
Before & After
Without hipr — manual config classes, validation, and factory methods:
from dataclasses import dataclass
# 1. Define the Config class (Boilerplate)
@dataclass
class OptimizerConfig:
learning_rate: float = 0.01
momentum: float = 0.9
# 2. Write a factory method to create the object
def make(self) -> "Optimizer":
return Optimizer(
learning_rate=self.learning_rate,
momentum=self.momentum
)
# 3. Define the actual class
class Optimizer:
def __init__(self, learning_rate: float, momentum: float):
self.learning_rate = learning_rate
self.momentum = momentum
# 4. Repeat for every component...
@dataclass
class ModelConfig:
optimizer: OptimizerConfig
hidden_size: int = 128
def make(self) -> "Model":
return Model(
optimizer=self.optimizer.make(),
hidden_size=self.hidden_size
)
class Model:
def __init__(self, optimizer: Optimizer, hidden_size: int):
self.optimizer = optimizer
self.hidden_size = hidden_size
With hipr — automatic config generation with validation:
from dataclasses import dataclass
from hipr import configurable, Hyper, Gt, Ge, Lt, DEFAULT
@configurable
@dataclass
class Optimizer:
learning_rate: Hyper[float, Gt[0.0]] = 0.01
momentum: Hyper[float, Ge[0.0]] = 0.9
@configurable
@dataclass
class Model:
hidden_size: Hyper[int, Ge[1]] = 128
dropout: Hyper[float, Ge[0.0], Lt[1.0]] = 0.1
optimizer: Hyper[Optimizer] = DEFAULT # Nested config with default
# Automatic validation
config = Model.Config(hidden_size=256, dropout=0.2)
# config = Model.Config(dropout=1.5) # ValidationError: must be < 1.0
# Serialize/deserialize
json_str = config.model_dump_json()
loaded = Model.Config.model_validate_json(json_str)
# Instantiate
model = config.make() # Returns Model instance with nested Optimizer
Installation
pip install hipr
# or: uv add hipr
Core Concepts
Classes & Dataclasses (Primary Use Case)
The @configurable decorator generates a .Config class that captures Hyper[T] parameters:
from dataclasses import dataclass
from hipr import configurable, Hyper, Ge
@configurable
@dataclass
class Optimizer:
learning_rate: Hyper[float] = 0.01
momentum: Hyper[float, Ge[0.0]] = 0.9
# Direct instantiation still works
opt = Optimizer(learning_rate=0.001)
# Or use Config for validation + serialization
config = Optimizer.Config(learning_rate=0.001)
opt = config.make() # Returns Optimizer instance
Regular classes work the same way:
@configurable
class Model:
def __init__(
self,
hidden_size: Hyper[int, Ge[1]] = 128,
dropout: Hyper[float] = 0.1,
):
self.hidden_size = hidden_size
self.dropout = dropout
config = Model.Config(hidden_size=256)
model = config.make() # Returns Model instance
Functions (Syntactic Sugar)
Functions work too—under the hood, @configurable treats them as a class with a __call__ method:
@configurable
def train(
data: list[float], # Runtime data (not a hyperparameter)
epochs: Hyper[int, Ge[1]] = 100,
lr: Hyper[float, Gt[0.0]] = 0.001,
) -> dict:
return {"trained": True}
Is handled internally as:
@configurable
@dataclass
class train:
epochs: Hyper[int, Ge[1]] = 100
lr: Hyper[float, Gt[0.0]] = 0.001
def __call__(self, data: list[float]) -> dict:
return {"trained": True}
The Hyper[T] Annotation
Mark tunable parameters with Hyper[T]:
# Simple type
count: Hyper[int] = 10
# With constraints
rate: Hyper[float, Ge[0.0], Le[1.0]] = 0.5
size: Hyper[int, Ge[1], Le[1000]] = 100
# Required parameter (no default)
learning_rate: Hyper[float, Gt[0.0]] # Must be provided
Available constraints: Ge, Gt, Le, Lt (numeric bounds), MinLen, MaxLen (length), MultipleOf, Pattern (regex). See constraint docstrings for details.
Nested Configurations
Compose configs hierarchically with DEFAULT:
@configurable
@dataclass
class Pipeline:
model: Hyper[Model] = DEFAULT # Uses Model's defaults
optimizer: Hyper[Optimizer] = DEFAULT
# Override nested values
config = Pipeline.Config(
model=Model.Config(hidden_size=512),
optimizer=Optimizer.Config(learning_rate=0.001),
)
pipeline = config.make() # All nested configs are instantiated
After make(), nested fields are instances, not configs:
print(pipeline.model.hidden_size) # 512
print(pipeline.optimizer.learning_rate) # 0.001
Literal Types & Enums
Use Literal or Enum for fixed choices:
from typing import Literal
from enum import Enum
class Mode(str, Enum):
FAST = "fast"
SLOW = "slow"
@configurable
def process(
mode: Hyper[Literal["train", "eval"]] = "train",
priority: Hyper[Mode] = Mode.FAST,
) -> str:
return f"{mode}, {priority.value}"
Type Checking
Generate .pyi stubs for full IDE support:
# Generate stubs for your source
hipr-generate-stubs src/
# Or specific files
hipr-generate-stubs my_module.py "lib/**/*.py"
Add to your workflow:
# pyproject.toml
[tool.poe.tasks]
stubs = "hipr-generate-stubs src/"
Serialization
Configs are Pydantic models with full serialization support:
config = Model.Config(hidden_size=256)
# To dict/JSON
config.model_dump()
config.model_dump_json()
# From dict/JSON
Model.Config(**some_dict)
Model.Config.model_validate_json(json_string)
Advanced Features
Methods
Instance methods also work (self is passed automatically):
class Analyzer:
@configurable
def detect(self, data: list, threshold: Hyper[float] = 3.0) -> list:
return [x for x in data if x > threshold]
analyzer = Analyzer()
config = analyzer.detect.Config(threshold=2.0)
result = config.make()(analyzer, [1, 2, 3, 4])
Validation & Safety
- Constraint conflicts detected at decoration time:
Hyper[int, Ge[10], Le[5]]→ error - Invalid regex patterns caught immediately
- Circular dependencies in nested configs → error
- Reserved names:
model_configis reserved by Pydantic
Thread Safety
@configurable is thread-safe for concurrent config creation and usage.
Comparison
| Feature | hipr | gin-config | hydra | tyro |
|---|---|---|---|---|
| Philosophy | Config from code | Dependency injection | YAML-first | CLI from types |
| Error Detection | Decoration + Runtime | Runtime only | Runtime | CLI parse time |
| Type Checking | Full (.pyi stubs) | None | Partial | Full |
| Boilerplate | Minimal | Minimal | Moderate | Minimal |
| Serialization | Pydantic native | Custom | YAML | YAML/JSON |
| Best For | Type-safe APIs, ML experiments | Google-style DI | Complex multi-run | CLI tools |
Performance
- Config creation: <100µs (Pydantic validation)
make()overhead: <50µs- Direct function call: zero overhead
Contributing
Contributions welcome! See CONTRIBUTING.md.
License
MIT License
hipr — Configuration should be effortless.
Project details
Release history Release notifications | RSS feed
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 hipr-0.1.7.tar.gz.
File metadata
- Download URL: hipr-0.1.7.tar.gz
- Upload date:
- Size: 70.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ef35b91526af69e4b056a4e707a98d5f569332eee71ba6a2303527773c29bcb
|
|
| MD5 |
8c649dc106dd77d07a40790f46bfe989
|
|
| BLAKE2b-256 |
275e2f73ca943cc573ca3fd3088d36e71f32b0f74d2eb96144433494c38851d5
|
File details
Details for the file hipr-0.1.7-py3-none-any.whl.
File metadata
- Download URL: hipr-0.1.7-py3-none-any.whl
- Upload date:
- Size: 33.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13be3b0577a5aafbc125f62a9de6ea749d207bde313888d0fa0d5cadc60d2bb6
|
|
| MD5 |
28a123e59553be2d377447b705732dec
|
|
| BLAKE2b-256 |
2ea467e93a5cbe0931812ae886dbb5062c5af42f02e8646c3efd0a98dfb3c06d
|