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: <100µs (Pydantic validation)
  • make() overhead: <50µs
  • Direct function call: zero overhead

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.9.tar.gz (72.2 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.9-py3-none-any.whl (34.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: hipr-0.1.9.tar.gz
  • Upload date:
  • Size: 72.2 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.9.tar.gz
Algorithm Hash digest
SHA256 89648975f6eded1dca143b116a85e651cd84a0520c3bb24ce2bb1113fc074e54
MD5 27c6ca5a442f8d484b71633152084e52
BLAKE2b-256 d830c030d6d123cd4a5afe2c517875580996ad2010829698d7cee94248768818

See more details on using hashes here.

File details

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

File metadata

  • Download URL: hipr-0.1.9-py3-none-any.whl
  • Upload date:
  • Size: 34.6 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.9-py3-none-any.whl
Algorithm Hash digest
SHA256 ee8d16dd417c212940a7f59486f9b4b3cf5c202d746b3fcfcd672beacbd87a91
MD5 a7db2c557e40567c9e8a2a019de6a539
BLAKE2b-256 14b7900eb0b9a966a8b2f05f6ee387eb42878ff910a2217d969ab2b298f9e1ff

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