Skip to main content

Automatic Pydantic config generation from function signatures with hyperparameters

Project description

hipr

(pronounced "hyper")

CI codecov

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, DEFAULT

@configurable
@dataclass
class Optimizer:
    learning_rate: float = 0.01
    momentum: float = 0.9

@configurable
@dataclass
class Model:
    hidden_size: int = 128
    dropout: float = 0.1
    optimizer: Optimizer = DEFAULT  # Nested config with default

# Create config
config = Model.Config(hidden_size=256, dropout=0.2)

# Serialize/deserialize
json_str = config.model_dump_json()
loaded = Model.Config.model_validate_json(json_str)

# Instantiate
model = config.make()
# model.optimizer is now an instance of Optimizer
print(model.optimizer.learning_rate)  # 0.01

Installation

pip install hipr
# or: uv add hipr

Core Concepts

Classes & Dataclasses (Primary Use Case)

The @configurable decorator generates a .Config class that captures parameters:

from dataclasses import dataclass
from hipr import configurable

@configurable
@dataclass
class Optimizer:
    learning_rate: float = 0.01
    momentum: float = 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: int = 128,
        dropout: 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)

For functions, the Hyper[T] annotation is required to identify which parameters are configurable.

from hipr import configurable, Hyper, Ge, Gt

@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:

This conceptual representation shows how hipr treats configurable functions as if they were dataclasses with a __call__ method, where the Hyper[T] parameters become fields of the dataclass.

@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}

Constraints & Validation

The Hyper[T] annotation allows you to attach validation constraints to your parameters. This works for both classes and functions (where it is required).

from dataclasses import dataclass
from hipr import configurable, Hyper, Ge, Le, Gt, MinLen, Pattern

@configurable
@dataclass
class Network:
    # Required parameter (no default) - Must come first in dataclass
    learning_rate: Hyper[float, Gt[0.0]]

    # Numeric bounds
    dropout: Hyper[float, Ge[0.0], Le[1.0]] = 0.5
    layers: Hyper[int, Ge[1], Le[100]] = 10

    # String constraints
    name: Hyper[str, MinLen[3], Pattern[r"^[a-z]+$"]] = "net"

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

Nested Configurations

Compose configs hierarchically with DEFAULT:

@configurable
@dataclass
class Pipeline:
    model: Model = DEFAULT      # Uses Model's defaults
    optimizer: 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:

print(pipeline.model.hidden_size)      # 512
print(pipeline.optimizer.learning_rate)  # 0.001

Collections & Lists

You can use standard typed collections like list[T] and dict[str, T] for configurable objects. hipr automatically allows passing either instances or Config objects for these items.

from hipr import configurable, DEFAULT
from typing import Any

@configurable
@dataclass
class Layer:
    size: int = 10

@configurable
@dataclass
class Network:
    # Use standard typing - hipr handles the rest
    layers: list[Layer] = DEFAULT

config = Network.Config(
    layers=[
        Layer.Config(size=32),
        Layer.Config(size=64),
    ]
)
net = config.make()
# net.layers is now [Layer(size=32), Layer(size=64)]

Nested Functions

You can also nest configurable functions using the .Type attribute exposed by the decorator. This allows hipr to automatically manage the function's configuration.

@configurable
def activation(x: float, limit: Hyper[float] = 1.0) -> float:
    return min(x, limit)

@configurable
@dataclass
class Layer:
    # Use .Type to nest the function configuration.
    # Note: # type: ignore is required if you are working in the same file
    # with the definition of activation. For other files, the generated stubs
    # will correctly expose .Type as a class.
    act_fn: activation.Type = DEFAULT  # type: ignore

config = Layer.Config()
layer = config.make()

# layer.act_fn is now a callable with 'limit' pre-bound
print(layer.act_fn(2.0))  # Output: 1.0

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
class Processor:
    def __init__(
        self,
        mode: Literal["train", "eval"] = "train",
        priority: Mode = Mode.FAST,
    ):
        self.mode = mode
        self.priority = priority

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_config is 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: ~2µs
  • make() overhead: ~0.1µs
  • Direct function call overhead: ~0.4µs vs raw function
  • Full pattern Config().make()(): ~6µs

See benchmarks/ for detailed measurements.

Contributing

Contributions welcome! Please open an issue or pull request.

License

MIT License


hipr — Configuration should be effortless.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hipr-0.1.11.tar.gz (68.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hipr-0.1.11-py3-none-any.whl (28.4 kB view details)

Uploaded Python 3

File details

Details for the file hipr-0.1.11.tar.gz.

File metadata

  • Download URL: hipr-0.1.11.tar.gz
  • Upload date:
  • Size: 68.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for hipr-0.1.11.tar.gz
Algorithm Hash digest
SHA256 348e9df1e15bc151d5494f322aec26effe3fc01ccb3e4cbaa973ffcd415d2f78
MD5 120fe1fbeb8896fcc733b4608ad66d19
BLAKE2b-256 87e8aa75b3fe090ca3bd5615ece8ed81bc0f81904cc1f30987a159780f27860a

See more details on using hashes here.

File details

Details for the file hipr-0.1.11-py3-none-any.whl.

File metadata

  • Download URL: hipr-0.1.11-py3-none-any.whl
  • Upload date:
  • Size: 28.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for hipr-0.1.11-py3-none-any.whl
Algorithm Hash digest
SHA256 2ecb15e99fddc3218852b971ea3b38de5997399fd11a0a176b95e3077418e501
MD5 882dc2f325c08dd8620cf949f4deca6b
BLAKE2b-256 9e791b5f3ca314844b605dad5d4447f22b34dc737634319f6e39be8b92c8cdd8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page