Name | configuronic JSON |
Version |
0.2.0
JSON |
| download |
home_page | None |
Summary | Simple yet powerful "Configuration as Code" library |
upload_time | 2025-08-19 15:48:17 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.10 |
license | Copyright (c) 2024-2025 Positronic Robotics Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
keywords |
configuration
config
dependency-injection
yaml
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# Configuronic
[](https://www.python.org/downloads/)
[](https://opensource.org/licenses/MIT)
[](https://badge.fury.io/py/configuronic)
[](https://github.com/Positronic-Robotics/configuronic/actions)
**Configuronic** is a simple yet powerful "Configuration as Code" library designed for modern Python applications, particularly in robotics, machine learning, and complex system configurations. Born from the need for a cleaner alternative to existing configuration frameworks, configuronic embraces Python's native syntax while providing powerful CLI integration and hierarchical configuration management.
## ✨ Why Configuronic?
* 🎯 **DRY Principle**: Write configurations in Python, not YAML/JSON/XML
* 🚀 **CLI-First**: Automatic command-line interfaces with complex nested parameter support
* 🔧 **Simple & Minimal**: Clean API that gets out of your way
* 🌳 **Hierarchical**: Deep nesting and inheritance support
* 🔄 **Dynamic**: Runtime configuration resolution with relative imports
## 🚀 Quick Start
```python
import configuronic as cfn
@cfn.config(learning_rate=0.001, epochs=100)
def train_model(learning_rate: float, epochs: int, model_name: str = "bert-base"):
print(f"Training {model_name} for {epochs} epochs with lr={learning_rate}")
# Your training logic here
if __name__ == "__main__":
cfn.cli(train_model)
```
Run from command line:
```bash
# Use defaults
python train.py
# Override parameters
python train.py --learning_rate=0.0001 --epochs=50 --model_name="gpt-2"
# See current configuration
python train.py --help
```
## 📖 Table of Contents
- [Installation](#-installation)
- [Core Concepts](#-core-concepts)
- [Real-World Examples](#-real-world-examples)
- [Advanced Features](#-advanced-features)
- [CLI Usage](#-cli-usage)
- [API Reference](#-api-reference)
- [Best Practices](#-best-practices)
- [Contributing](#-contributing)
## 📦 Installation
Using pip
```bash
pip install configuronic
```
### Development Installation
```bash
git clone https://github.com/Positronic-Robotics/configuronic.git
cd configuronic
uv venv -p 3.10
source .venv/bin/activate
uv pip install -e .[dev]
```
## 🧠 Core Concepts
### Configuration as Code
In configuronic, configurations are **closures** - callables that store both the function and its arguments. It is somewhat similar to [`functools.partial`](https://docs.python.org/3/library/functools.html#functools.partial) This functional approach enables powerful composition and inheritance patterns.
```python
import configuronic as cfn
# Create a configuration
@cfn.config(batch_size=32, lr=0.001)
def create_optimizer(batch_size: int, lr: float):
return torch.optim.Adam(lr=lr)
# Override and create variants
fast_optimizer = create_optimizer.override(lr=0.01)
large_batch_optimizer = create_optimizer.override(batch_size=128)
# Instantiate when needed
optimizer = fast_optimizer.instantiate()
```
### Two Main Operations
**1. `override(**kwargs)`** - Create configuration variants
```python
base_config = cfn.Config(MyModel, layers=3, units=64)
deep_config = base_config.override(layers=6)
wide_config = base_config.override(units=128)
```
**2. `instantiate()`** - Execute function with configured arguments and get its result
```python
model = deep_config.instantiate() # Returns MyModel(layers=6, units=64)
```
**Callable Syntax** - Config objects are callable, providing a shorthand for override + instantiate
```python
# These are equivalent:
result1 = config.override(param=value).instantiate()
result2 = config(param=value)()
```
### Nested Configuration Override
Support for deep parameter modification using dot notation:
```python
# Configure a complex training pipeline
training_cfg = cfn.Config(
train_pipeline,
model=cfn.Config(TransformerModel, layers=6, hidden_size=512),
optimizer=cfn.Config(torch.optim.Adam, lr=0.001),
data=cfn.Config(DataLoader, batch_size=32)
)
# Override nested parameters
fast_training = training_cfg.override(**{
"optimizer.lr": 0.01,
"data.batch_size": 64,
"model.layers": 12
})
```
## 🌍 Real-World Examples
### Robotics Hardware Configuration
```python
import configuronic as cfn
@cfn.config(ip="172.168.0.2")
def robot_arm(ip: str, relative_dynamics_factor: float = 0.2):
from my_robots import FrankaArm
return FrankaArm(ip=ip, dynamics_factor=relative_dynamics_factor)
@cfn.config(device_path="/dev/video0", fps=30)
def camera(device_path: str, width: int = 1920, height: int = 1080, fps: int = 30):
from my_cameras import Camera
return Camera(device_path, width, height, fps)
# Create specific hardware configurations
left_camera = camera.override(device_path="/dev/video1")
right_camera = camera.override(device_path="/dev/video2")
# Main system configuration
@cfn.config(arm=robot_arm,
cameras={'left': left_cam, 'right': right_cam})
def main(arm, cameras, gripper=None):
from robot_library import RobotSystem
system = RobotSystem(arm=arm, cameras=cameras, gripper=gripper)
system.run()
if __name__ == "__main__":
cfn.cli(main)
```
### Machine Learning Pipeline
```python
@cfn.config(model_name="bert-base", max_length=512)
def create_tokenizer(model_name: str, max_length: int):
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.model_max_length = max_length
return tokenizer
@cfn.config(hidden_size=768, num_layers=12)
def create_model(hidden_size: int, num_layers: int, tokenizer):
vocab_size = len(tokenizer)
return TransformerModel(vocab_size, hidden_size, num_layers)
# Configure the complete pipeline
@cfn.config()
def training_pipeline(
tokenizer=create_tokenizer,
model=create_model,
learning_rate: float = 1e-4,
batch_size: int = 16
):
# Pipeline implementation
return TrainingPipeline(tokenizer, model, learning_rate, batch_size)
if __name__ == "__main__":
cfn.cli(training_pipeline)
```
Run with different configurations:
```bash
# Use defaults
python train.py
# Quick experiments
python train.py --learning_rate=1e-3 --batch_size=32
# Override nested model parameters
python train.py --model.num_layers=6 --tokenizer.max_length=256
# Switch to different model entirely
python train.py --tokenizer.model_name="gpt2" --model.hidden_size=1024
```
## 🔧 Advanced Features
### Import Resolution with `@` and `.`
Configuronic provides powerful import resolution syntax that allows you to dynamically reference Python objects, especially useful for CLI usage.
#### Absolute Imports (`@`)
Direct import paths to any Python object. If you need to use a literal `@` at the beginning of a string (not for imports), use `@@`:
```bash
# From command line - these import exact module paths
python train.py --model="@transformers.BertModel" # Import BertModel
python train.py --message="@@starts_with_at" # Literal string "@starts_with_at"
python train.py --text="foo@bar" # No escaping needed in the middle
```
Absolute imports could also be used in both Python decorators and classes. However, we don't suggest using them:
```python
@cfn.config(model="@torchvision.models.resnet34")
def get_model_parameters(model: torch.nn.Module):
return list(model.named_parameters())
```
```python
def get_model_parameters(model: torch.nn.Module):
return list(model.named_parameters())
resnet_model_parameters = cfn.Config(
get_model_parameters,
model="@torchvision.models.resnet34"
)
```
#### Relative Imports (`.`)
Navigate relative to the current module, similar to Python's relative import syntax:
```python
# If default is myproject.models.BertEncoder
python train.py --encoder=".RobertaEncoder" # -> myproject.models.RobertaEncoder (same module)
python train.py --encoder="..utils.CustomEncoder" # -> myproject.utils.CustomEncoder (parent module)
python train.py --encoder="...shared.BaseEncoder" # -> myproject.shared.BaseEncoder (grandparent module)
```
**How it works:** Each `.` acts like `../` in file system navigation:
- `.` = stay in current module (like `./`)
- `..` = go up one module level (like `../`)
- `...` = go up two module levels (like `../../`), etc.
The path after the dots specifies the target within that module hierarchy.
#### Configuration Copy Across Modules
The `copy()` method updates module context so relative imports (`.`) resolve from the new module location:
```python
# configs/base.py
original_config = cfn.Config(SomeClass, value=1)
# experiments/vision.py
from configs.base import original_config
# Copy updates the config's module context to experiments.vision
copied_config = original_config.copy()
@cfn.config()
def local_function():
return "local result"
# When copied_config is used as default, '.' resolves in experiments.vision
env_cfg = cfn.Config(Environment, setup=copied_config)
specialized_cfg = env_cfg.override(setup=".local_function") # Finds local_function
```
Without `copy()`, `.local_function` would try to resolve in `configs.base` and fail.
### Lists and Dictionaries
Configuronic seamlessly handles nested data structures:
```python
simulation_cfg = cfn.Config(
run_simulation,
loaders=[
cfn.Config(AddCameras, camera_config=camera_cfg),
cfn.Config(AddObjects, objects=["cube", "sphere"]),
cfn.Config(SetLighting, intensity=0.8)
],
cameras={
'main': cfn.Config(Camera, position=[0, 0, 1]),
'side': cfn.Config(Camera, position=[1, 0, 0])
}
)
# Override specific items
modified_sim = simulation_cfg.override(**{
"loaders.0.camera_config.fps": 60, # First loader's camera FPS
"cameras.main.position": [0, 0, 2] # Main camera position
})
```
### Configuration Inheritance
```python
# Base configuration
base_camera = cfn.Config(Camera, width=1920, height=1080, fps=30)
# Derived configurations
hd_camera = base_camera.override(width=1280, height=720)
high_fps_camera = base_camera.override(fps=60)
webcam = base_camera.override(width=640, height=480, fps=15)
# All inherit base settings unless overridden
```
## 🖥️ CLI Usage
Configuronic leverages [Python Fire](https://github.com/google/python-fire) for automatic CLI generation:
### Basic CLI
```python
@cfn.config(param1="default", param2=42)
def my_function(param1: str, param2: int):
return f"{param1}: {param2}"
if __name__ == "__main__":
cfn.cli(my_function)
```
### Command Line Examples
```bash
# Show help and current config
python script.py --help
# Override parameters
python script.py --param1="hello" --param2=100
# Nested parameter override
python script.py --model.layers=6 --optimizer.lr=0.001
# Using absolute imports
python script.py --model="@my_models.CustomTransformer"
# To pass a string that starts with @, repeat it twice
python script.py --message="@@this_is_literal_at_sign"
# Using relative imports
python script.py --tokenizer=".CustomTokenizer"
# Complex nested overrides
python script.py --cameras.left.fps=60 --cameras.right.device="/dev/video2"
```
### Multi-Command CLI
You can also provide multiple commands in a single script by passing a dictionary of configurations:
```python
@cfn.config()
def sum_numbers(x: float, y: float) -> float:
"""Sum of two numbers"""
return x + y
@cfn.config()
def multiply_numbers(x: float, y: float) -> float:
"""Product of two numbers"""
return x * y
if __name__ == "__main__":
cfn.cli({'sum': sum_numbers, 'multiply': multiply_numbers})
```
This enables running different commands from the same script:
```bash
# Run sum command
python script.py sum --x=5 --y=10
# Output: 15.0
# Run multiply command
python script.py multiply --x=5 --y=10
# Output: 50.0
# Get help for all commands
python script.py --help
# Shows available commands with their descriptions
# Get help for specific command
python script.py sum --help
# Shows detailed help for the sum command
```
### Parameter Override Order ⚠️
**Important:** Parameter overrides are executed in order of declaration. When overriding nested configurations, set the parent object first, then its properties:
```bash
# ✅ Correct: set camera first, then its resolution
python script.py --camera="@opencv.Camera" --camera.resolution="full_hd"
# ❌ Incorrect: this will reset camera after setting resolution
python script.py --camera.resolution="full_hd" --camera="@opencv.Camera"
```
In the incorrect example, the default camera's resolution gets updated first, but then the entire camera object is replaced, losing the resolution override.
## 📚 API Reference
### Core Classes
#### `Config(target, *args, **kwargs)`
Main configuration class that stores a callable and its arguments.
**Methods:**
- `override(**kwargs) -> Config`: Create new config with updated parameters
- `instantiate() -> Any`: Execute the configuration and return result
- `copy() -> Config`: Deep copy the configuration
- `__call__(**kwargs) -> Any`: `override` config with `**kwargs` and `instantiate` it. **Note:** only keyword specified arguments are supported.
#### `@config` Decorator
```python
@cfn.config # No override, just turn function into config.
def print_greeting(greeting: str = 'Hello', entity: str = 'world'):
print(f'{greeting} {entity}!')
@cfn.config(arg1="default", arg2=42) # With defaults
def my_function(...):
pass
```
### Utility Functions
#### `cli(config: Config | dict[str, Config])`
Generate automatic command-line interface for any configuration or multiple configurations.
**Parameters:**
- `config`: Either a single `Config` object or a dictionary mapping command names to `Config` objects
**Examples:**
```python
# Single command
cfn.cli(my_config)
# Multiple commands
cfn.cli({'train': train_config, 'eval': eval_config, 'test': test_config})
```
#### `get_required_args(config: Config) -> List[str]`
Get list of required arguments for a configuration.
### Special Syntax
- `@module.path.Class` - Absolute import path to any Python object
- `.RelativeClass` - Relative import (same module, like `./`)
- `..parent.Class` - Relative import (up one level, like `../`)
- `@@literal_string` - Escape literal `@` characters in the beginning of strings
> **Path Resolution:** The `.` syntax works like file system navigation where each dot moves up one module level in the Python package hierarchy, then navigates down to the specified target.
## 💡 Best Practices
### 1. Avoid Positional Arguments (`*args`) ⚠️
> **Warning:** Configuronic has limited support for positional arguments (`*args`). Use them only in exceptional cases (like functions that accept variable-length lists) and always use explicit `cfn.Config()` construction, never the decorator.
**Problems with positional arguments:**
1. **Implicit and unclear** - Hard to understand what arguments represent:
```python
# ❌ Unclear what 1, 2, 3 represent
func = cfn.Config(func, 1, 2, 3)
```
2. **Fragile** - Changing argument order breaks all configurations:
```python
# ❌ If you change parameter order, all configs break
@cfn.config("robot", "camera")
def compose(camera, robot): # Swapped order!
...
```
3. **Ambiguous override behavior** - Unclear what `override()` should do:
```python
# ❌ What should sum_with45 contain? [4, 5] or [1, 2, 3, 4, 5]?
sum_123 = cfn.Config(sum, 1, 2, 3)
sum_with45 = sum_123.override(4, 5)
```
**Recommended approach:**
```python
# ✅ Use keyword arguments for clarity
config = cfn.Config(func, param1=1, param2=2, param3=3)
# ✅ Only use *args for functions designed for them
@cfn.config() # No positional args in decorator
def sum_all(*numbers):
return sum(numbers)
variadic_sum = cfn.Config(sum_all, 1, 2, 3) # Explicit Config only
```
### 2. Create Separate Modules for Configurations
If you don't want your business logic modules to depend on `configuronic`, it's wise to have a separate package for configurations.
```python
# configs/models.py
transformer_base = cfn.Config(TransformerModel, layers=6, hidden_size=512)
transformer_large = transformer_base.override(layers=12, hidden_size=1024)
# configs/training.py
from .models import transformer_base
training_pipeline = cfn.Config(TrainingPipeline, model=transformer_base)
```
### 3. Import Inside Configuration Functions
In robotic applications, some configurations may depend on parituclar hardware and Python packages that provide drivers, that are not always available. If you don't want to force your users to install all of them, consider making imports inside the functions that you configure.
```python
@cfn.config()
def create_model(layers: int = 6):
from my_project.models import TransformerModel
return TransformerModel(layers=layers)
```
But in smaller projects it might be very convinient to put configurations alongside the methods they manage.
### 4. Use `override` to create as many custom configurations as you need
When working with text configuration files, it's natural to create separate files for different environments or use cases. In `configuronic`, just create new configuration variables that override the base config.
```python
# Base training configuration
base_training = cfn.Config(
TrainingPipeline,
model=cfn.Config(TransformerModel, layers=6, hidden_size=512),
optimizer=cfn.Config(torch.optim.Adam, lr=0.001),
batch_size=32,
epochs=10
)
# Development environment - smaller, faster
dev_training = base_training.override(
batch_size=8, epochs=2,
**{"model.layers": 3, "model.hidden_size": 256})
# Production environment - optimized settings
prod_training = base_training.override(
batch_size=64, epochs=100,
**{"optimizer.lr": 0.0001})
# Experimental setup - large model
experimental_training = base_training.override(
**{
"model.layers": 12,
"model.hidden_size": 1024,
"optimizer.lr": 0.0005,
"batch_size": 16
})
# Quick debugging setup
debug_training = base_training.override(
epochs=1, batch_size=2, **{"model.layers": 1})
# Now you can easily switch between configurations
# TODO: Make this part of Configuronic
if __name__ == "__main__":
import sys
configs = {
'dev': dev_training,
'prod': prod_training,
'experimental': experimental_training,
'debug': debug_training
}
config_name = sys.argv[1] if len(sys.argv) > 1 else 'dev'
selected_config = configs.get(config_name, dev_training)
cfn.cli(selected_config)
```
**Usage:**
```bash
python train.py dev # Use development config
python train.py prod # Use production config
python train.py experimental # Use experimental config
python train.py debug # Use debug config
# Still supports all override capabilities
python train.py prod --epochs=50 --batch_size=128
```
## 🤝 Contributing
We welcome contributions! Here's how to get started:
### Development Setup
```bash
git clone https://github.com/Positronic-Robotics/configuronic.git
cd configuronic
uv pip install -e ".[dev]"
```
### Running Tests
```bash
pytest # Run all tests
pytest --cov=configuronic # Run with coverage
```
### 📋 Guidelines
- Follow existing code style and patterns
- Add tests for new functionality
- Ensure all tests pass before submitting
- Update documentation as needed
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details.
## 📞 Support
- 🐛 **Bug Reports**: [GitHub Issues](https://github.com/Positronic-Robotics/configuronic/issues)
- 💬 **Discussions**: [GitHub Discussions](https://github.com/Positronic-Robotics/configuronic/discussions) **FIXME: Create Discord**
- 📧 **Email**: hi@positronic.ro
---
**⭐ If you find Configuronic useful, please consider giving it a star on GitHub!**
Raw data
{
"_id": null,
"home_page": null,
"name": "configuronic",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "configuration, config, dependency-injection, yaml",
"author": null,
"author_email": "Positronic Robotics <hi@positronic.ro>",
"download_url": "https://files.pythonhosted.org/packages/ab/4c/6fd7aeba337632085c677e93a5c667daa4f6c46e5e44cdfb9f9e9097f172/configuronic-0.2.0.tar.gz",
"platform": null,
"description": "# Configuronic\n\n[](https://www.python.org/downloads/)\n[](https://opensource.org/licenses/MIT)\n[](https://badge.fury.io/py/configuronic)\n[](https://github.com/Positronic-Robotics/configuronic/actions)\n\n**Configuronic** is a simple yet powerful \"Configuration as Code\" library designed for modern Python applications, particularly in robotics, machine learning, and complex system configurations. Born from the need for a cleaner alternative to existing configuration frameworks, configuronic embraces Python's native syntax while providing powerful CLI integration and hierarchical configuration management.\n\n## \u2728 Why Configuronic?\n\n* \ud83c\udfaf **DRY Principle**: Write configurations in Python, not YAML/JSON/XML\n* \ud83d\ude80 **CLI-First**: Automatic command-line interfaces with complex nested parameter support\n* \ud83d\udd27 **Simple & Minimal**: Clean API that gets out of your way\n* \ud83c\udf33 **Hierarchical**: Deep nesting and inheritance support\n* \ud83d\udd04 **Dynamic**: Runtime configuration resolution with relative imports\n\n## \ud83d\ude80 Quick Start\n\n```python\nimport configuronic as cfn\n\n@cfn.config(learning_rate=0.001, epochs=100)\ndef train_model(learning_rate: float, epochs: int, model_name: str = \"bert-base\"):\n print(f\"Training {model_name} for {epochs} epochs with lr={learning_rate}\")\n # Your training logic here\n\nif __name__ == \"__main__\":\n cfn.cli(train_model)\n```\n\nRun from command line:\n```bash\n# Use defaults\npython train.py\n\n# Override parameters\npython train.py --learning_rate=0.0001 --epochs=50 --model_name=\"gpt-2\"\n\n# See current configuration\npython train.py --help\n```\n\n## \ud83d\udcd6 Table of Contents\n\n- [Installation](#-installation)\n- [Core Concepts](#-core-concepts)\n- [Real-World Examples](#-real-world-examples)\n- [Advanced Features](#-advanced-features)\n- [CLI Usage](#-cli-usage)\n- [API Reference](#-api-reference)\n- [Best Practices](#-best-practices)\n- [Contributing](#-contributing)\n\n## \ud83d\udce6 Installation\n\nUsing pip\n```bash\npip install configuronic\n```\n\n### Development Installation\n```bash\ngit clone https://github.com/Positronic-Robotics/configuronic.git\ncd configuronic\nuv venv -p 3.10\nsource .venv/bin/activate\nuv pip install -e .[dev]\n```\n\n## \ud83e\udde0 Core Concepts\n\n### Configuration as Code\n\nIn configuronic, configurations are **closures** - callables that store both the function and its arguments. It is somewhat similar to [`functools.partial`](https://docs.python.org/3/library/functools.html#functools.partial) This functional approach enables powerful composition and inheritance patterns.\n\n```python\nimport configuronic as cfn\n\n# Create a configuration\n@cfn.config(batch_size=32, lr=0.001)\ndef create_optimizer(batch_size: int, lr: float):\n return torch.optim.Adam(lr=lr)\n\n# Override and create variants\nfast_optimizer = create_optimizer.override(lr=0.01)\nlarge_batch_optimizer = create_optimizer.override(batch_size=128)\n\n# Instantiate when needed\noptimizer = fast_optimizer.instantiate()\n```\n\n### Two Main Operations\n\n**1. `override(**kwargs)`** - Create configuration variants\n```python\nbase_config = cfn.Config(MyModel, layers=3, units=64)\ndeep_config = base_config.override(layers=6)\nwide_config = base_config.override(units=128)\n```\n\n**2. `instantiate()`** - Execute function with configured arguments and get its result\n```python\nmodel = deep_config.instantiate() # Returns MyModel(layers=6, units=64)\n```\n\n**Callable Syntax** - Config objects are callable, providing a shorthand for override + instantiate\n```python\n# These are equivalent:\nresult1 = config.override(param=value).instantiate()\nresult2 = config(param=value)()\n```\n\n### Nested Configuration Override\n\nSupport for deep parameter modification using dot notation:\n\n```python\n# Configure a complex training pipeline\ntraining_cfg = cfn.Config(\n train_pipeline,\n model=cfn.Config(TransformerModel, layers=6, hidden_size=512),\n optimizer=cfn.Config(torch.optim.Adam, lr=0.001),\n data=cfn.Config(DataLoader, batch_size=32)\n)\n\n# Override nested parameters\nfast_training = training_cfg.override(**{\n \"optimizer.lr\": 0.01,\n \"data.batch_size\": 64,\n \"model.layers\": 12\n})\n```\n\n## \ud83c\udf0d Real-World Examples\n\n### Robotics Hardware Configuration\n\n```python\nimport configuronic as cfn\n\n@cfn.config(ip=\"172.168.0.2\")\ndef robot_arm(ip: str, relative_dynamics_factor: float = 0.2):\n from my_robots import FrankaArm\n return FrankaArm(ip=ip, dynamics_factor=relative_dynamics_factor)\n\n@cfn.config(device_path=\"/dev/video0\", fps=30)\ndef camera(device_path: str, width: int = 1920, height: int = 1080, fps: int = 30):\n from my_cameras import Camera\n return Camera(device_path, width, height, fps)\n\n# Create specific hardware configurations\nleft_camera = camera.override(device_path=\"/dev/video1\")\nright_camera = camera.override(device_path=\"/dev/video2\")\n\n# Main system configuration\n@cfn.config(arm=robot_arm,\n cameras={'left': left_cam, 'right': right_cam})\ndef main(arm, cameras, gripper=None):\n from robot_library import RobotSystem\n system = RobotSystem(arm=arm, cameras=cameras, gripper=gripper)\n system.run()\n\nif __name__ == \"__main__\":\n cfn.cli(main)\n```\n\n### Machine Learning Pipeline\n\n```python\n@cfn.config(model_name=\"bert-base\", max_length=512)\ndef create_tokenizer(model_name: str, max_length: int):\n from transformers import AutoTokenizer\n tokenizer = AutoTokenizer.from_pretrained(model_name)\n tokenizer.model_max_length = max_length\n return tokenizer\n\n@cfn.config(hidden_size=768, num_layers=12)\ndef create_model(hidden_size: int, num_layers: int, tokenizer):\n vocab_size = len(tokenizer)\n return TransformerModel(vocab_size, hidden_size, num_layers)\n\n# Configure the complete pipeline\n@cfn.config()\ndef training_pipeline(\n tokenizer=create_tokenizer,\n model=create_model,\n learning_rate: float = 1e-4,\n batch_size: int = 16\n):\n # Pipeline implementation\n return TrainingPipeline(tokenizer, model, learning_rate, batch_size)\n\nif __name__ == \"__main__\":\n cfn.cli(training_pipeline)\n```\n\nRun with different configurations:\n```bash\n# Use defaults\npython train.py\n\n# Quick experiments\npython train.py --learning_rate=1e-3 --batch_size=32\n\n# Override nested model parameters\npython train.py --model.num_layers=6 --tokenizer.max_length=256\n\n# Switch to different model entirely\npython train.py --tokenizer.model_name=\"gpt2\" --model.hidden_size=1024\n```\n\n## \ud83d\udd27 Advanced Features\n\n### Import Resolution with `@` and `.`\n\nConfiguronic provides powerful import resolution syntax that allows you to dynamically reference Python objects, especially useful for CLI usage.\n\n#### Absolute Imports (`@`)\nDirect import paths to any Python object. If you need to use a literal `@` at the beginning of a string (not for imports), use `@@`:\n```bash\n# From command line - these import exact module paths\npython train.py --model=\"@transformers.BertModel\" # Import BertModel\npython train.py --message=\"@@starts_with_at\" # Literal string \"@starts_with_at\"\npython train.py --text=\"foo@bar\" # No escaping needed in the middle\n```\n\nAbsolute imports could also be used in both Python decorators and classes. However, we don't suggest using them:\n\n```python\n@cfn.config(model=\"@torchvision.models.resnet34\")\ndef get_model_parameters(model: torch.nn.Module):\n return list(model.named_parameters())\n```\n\n```python\ndef get_model_parameters(model: torch.nn.Module):\n return list(model.named_parameters())\n\nresnet_model_parameters = cfn.Config(\n get_model_parameters,\n model=\"@torchvision.models.resnet34\"\n)\n```\n\n#### Relative Imports (`.`)\nNavigate relative to the current module, similar to Python's relative import syntax:\n\n```python\n# If default is myproject.models.BertEncoder\npython train.py --encoder=\".RobertaEncoder\" # -> myproject.models.RobertaEncoder (same module)\npython train.py --encoder=\"..utils.CustomEncoder\" # -> myproject.utils.CustomEncoder (parent module)\npython train.py --encoder=\"...shared.BaseEncoder\" # -> myproject.shared.BaseEncoder (grandparent module)\n```\n\n**How it works:** Each `.` acts like `../` in file system navigation:\n- `.` = stay in current module (like `./`)\n- `..` = go up one module level (like `../`)\n- `...` = go up two module levels (like `../../`), etc.\n\nThe path after the dots specifies the target within that module hierarchy.\n\n#### Configuration Copy Across Modules\n\nThe `copy()` method updates module context so relative imports (`.`) resolve from the new module location:\n\n```python\n# configs/base.py\noriginal_config = cfn.Config(SomeClass, value=1)\n\n# experiments/vision.py\nfrom configs.base import original_config\n\n# Copy updates the config's module context to experiments.vision\ncopied_config = original_config.copy()\n\n@cfn.config()\ndef local_function():\n return \"local result\"\n\n# When copied_config is used as default, '.' resolves in experiments.vision\nenv_cfg = cfn.Config(Environment, setup=copied_config)\nspecialized_cfg = env_cfg.override(setup=\".local_function\") # Finds local_function\n```\n\nWithout `copy()`, `.local_function` would try to resolve in `configs.base` and fail.\n\n### Lists and Dictionaries\n\nConfiguronic seamlessly handles nested data structures:\n\n```python\nsimulation_cfg = cfn.Config(\n run_simulation,\n loaders=[\n cfn.Config(AddCameras, camera_config=camera_cfg),\n cfn.Config(AddObjects, objects=[\"cube\", \"sphere\"]),\n cfn.Config(SetLighting, intensity=0.8)\n ],\n cameras={\n 'main': cfn.Config(Camera, position=[0, 0, 1]),\n 'side': cfn.Config(Camera, position=[1, 0, 0])\n }\n)\n\n# Override specific items\nmodified_sim = simulation_cfg.override(**{\n \"loaders.0.camera_config.fps\": 60, # First loader's camera FPS\n \"cameras.main.position\": [0, 0, 2] # Main camera position\n})\n```\n\n### Configuration Inheritance\n\n```python\n# Base configuration\nbase_camera = cfn.Config(Camera, width=1920, height=1080, fps=30)\n\n# Derived configurations\nhd_camera = base_camera.override(width=1280, height=720)\nhigh_fps_camera = base_camera.override(fps=60)\nwebcam = base_camera.override(width=640, height=480, fps=15)\n\n# All inherit base settings unless overridden\n```\n\n## \ud83d\udda5\ufe0f CLI Usage\n\nConfiguronic leverages [Python Fire](https://github.com/google/python-fire) for automatic CLI generation:\n\n### Basic CLI\n```python\n@cfn.config(param1=\"default\", param2=42)\ndef my_function(param1: str, param2: int):\n return f\"{param1}: {param2}\"\n\nif __name__ == \"__main__\":\n cfn.cli(my_function)\n```\n\n### Command Line Examples\n```bash\n# Show help and current config\npython script.py --help\n\n# Override parameters\npython script.py --param1=\"hello\" --param2=100\n\n# Nested parameter override\npython script.py --model.layers=6 --optimizer.lr=0.001\n\n# Using absolute imports\npython script.py --model=\"@my_models.CustomTransformer\"\n\n# To pass a string that starts with @, repeat it twice\npython script.py --message=\"@@this_is_literal_at_sign\"\n\n# Using relative imports\npython script.py --tokenizer=\".CustomTokenizer\"\n\n# Complex nested overrides\npython script.py --cameras.left.fps=60 --cameras.right.device=\"/dev/video2\"\n```\n\n\n### Multi-Command CLI\nYou can also provide multiple commands in a single script by passing a dictionary of configurations:\n\n```python\n@cfn.config()\ndef sum_numbers(x: float, y: float) -> float:\n \"\"\"Sum of two numbers\"\"\"\n return x + y\n\n@cfn.config()\ndef multiply_numbers(x: float, y: float) -> float:\n \"\"\"Product of two numbers\"\"\"\n return x * y\n\nif __name__ == \"__main__\":\n cfn.cli({'sum': sum_numbers, 'multiply': multiply_numbers})\n```\n\nThis enables running different commands from the same script:\n\n```bash\n# Run sum command\npython script.py sum --x=5 --y=10\n# Output: 15.0\n\n# Run multiply command\npython script.py multiply --x=5 --y=10\n# Output: 50.0\n\n# Get help for all commands\npython script.py --help\n# Shows available commands with their descriptions\n\n# Get help for specific command\npython script.py sum --help\n# Shows detailed help for the sum command\n```\n\n### Parameter Override Order \u26a0\ufe0f\n\n**Important:** Parameter overrides are executed in order of declaration. When overriding nested configurations, set the parent object first, then its properties:\n\n```bash\n# \u2705 Correct: set camera first, then its resolution\npython script.py --camera=\"@opencv.Camera\" --camera.resolution=\"full_hd\"\n\n# \u274c Incorrect: this will reset camera after setting resolution\npython script.py --camera.resolution=\"full_hd\" --camera=\"@opencv.Camera\"\n```\n\nIn the incorrect example, the default camera's resolution gets updated first, but then the entire camera object is replaced, losing the resolution override.\n\n## \ud83d\udcda API Reference\n\n### Core Classes\n\n#### `Config(target, *args, **kwargs)`\nMain configuration class that stores a callable and its arguments.\n\n**Methods:**\n- `override(**kwargs) -> Config`: Create new config with updated parameters\n- `instantiate() -> Any`: Execute the configuration and return result\n- `copy() -> Config`: Deep copy the configuration\n- `__call__(**kwargs) -> Any`: `override` config with `**kwargs` and `instantiate` it. **Note:** only keyword specified arguments are supported.\n\n#### `@config` Decorator\n```python\n@cfn.config # No override, just turn function into config.\ndef print_greeting(greeting: str = 'Hello', entity: str = 'world'):\n print(f'{greeting} {entity}!')\n\n@cfn.config(arg1=\"default\", arg2=42) # With defaults\ndef my_function(...):\n pass\n```\n\n### Utility Functions\n\n#### `cli(config: Config | dict[str, Config])`\nGenerate automatic command-line interface for any configuration or multiple configurations.\n\n**Parameters:**\n- `config`: Either a single `Config` object or a dictionary mapping command names to `Config` objects\n\n**Examples:**\n```python\n# Single command\ncfn.cli(my_config)\n\n# Multiple commands\ncfn.cli({'train': train_config, 'eval': eval_config, 'test': test_config})\n```\n\n#### `get_required_args(config: Config) -> List[str]`\nGet list of required arguments for a configuration.\n\n### Special Syntax\n\n- `@module.path.Class` - Absolute import path to any Python object\n- `.RelativeClass` - Relative import (same module, like `./`)\n- `..parent.Class` - Relative import (up one level, like `../`)\n- `@@literal_string` - Escape literal `@` characters in the beginning of strings\n\n> **Path Resolution:** The `.` syntax works like file system navigation where each dot moves up one module level in the Python package hierarchy, then navigates down to the specified target.\n\n\n## \ud83d\udca1 Best Practices\n\n### 1. Avoid Positional Arguments (`*args`) \u26a0\ufe0f\n\n> **Warning:** Configuronic has limited support for positional arguments (`*args`). Use them only in exceptional cases (like functions that accept variable-length lists) and always use explicit `cfn.Config()` construction, never the decorator.\n\n**Problems with positional arguments:**\n\n1. **Implicit and unclear** - Hard to understand what arguments represent:\n ```python\n # \u274c Unclear what 1, 2, 3 represent\n func = cfn.Config(func, 1, 2, 3)\n ```\n\n2. **Fragile** - Changing argument order breaks all configurations:\n ```python\n # \u274c If you change parameter order, all configs break\n @cfn.config(\"robot\", \"camera\")\n def compose(camera, robot): # Swapped order!\n ...\n ```\n\n3. **Ambiguous override behavior** - Unclear what `override()` should do:\n ```python\n # \u274c What should sum_with45 contain? [4, 5] or [1, 2, 3, 4, 5]?\n sum_123 = cfn.Config(sum, 1, 2, 3)\n sum_with45 = sum_123.override(4, 5)\n ```\n\n**Recommended approach:**\n```python\n# \u2705 Use keyword arguments for clarity\nconfig = cfn.Config(func, param1=1, param2=2, param3=3)\n\n# \u2705 Only use *args for functions designed for them\n@cfn.config() # No positional args in decorator\ndef sum_all(*numbers):\n return sum(numbers)\n\nvariadic_sum = cfn.Config(sum_all, 1, 2, 3) # Explicit Config only\n```\n\n### 2. Create Separate Modules for Configurations\nIf you don't want your business logic modules to depend on `configuronic`, it's wise to have a separate package for configurations.\n```python\n# configs/models.py\ntransformer_base = cfn.Config(TransformerModel, layers=6, hidden_size=512)\ntransformer_large = transformer_base.override(layers=12, hidden_size=1024)\n\n# configs/training.py\nfrom .models import transformer_base\ntraining_pipeline = cfn.Config(TrainingPipeline, model=transformer_base)\n```\n\n### 3. Import Inside Configuration Functions\nIn robotic applications, some configurations may depend on parituclar hardware and Python packages that provide drivers, that are not always available. If you don't want to force your users to install all of them, consider making imports inside the functions that you configure.\n```python\n@cfn.config()\ndef create_model(layers: int = 6):\n from my_project.models import TransformerModel\n return TransformerModel(layers=layers)\n```\n\nBut in smaller projects it might be very convinient to put configurations alongside the methods they manage.\n\n### 4. Use `override` to create as many custom configurations as you need\nWhen working with text configuration files, it's natural to create separate files for different environments or use cases. In `configuronic`, just create new configuration variables that override the base config.\n\n```python\n# Base training configuration\nbase_training = cfn.Config(\n TrainingPipeline,\n model=cfn.Config(TransformerModel, layers=6, hidden_size=512),\n optimizer=cfn.Config(torch.optim.Adam, lr=0.001),\n batch_size=32,\n epochs=10\n)\n\n# Development environment - smaller, faster\ndev_training = base_training.override(\n batch_size=8, epochs=2,\n **{\"model.layers\": 3, \"model.hidden_size\": 256})\n\n# Production environment - optimized settings\nprod_training = base_training.override(\n batch_size=64, epochs=100,\n **{\"optimizer.lr\": 0.0001})\n\n# Experimental setup - large model\nexperimental_training = base_training.override(\n **{\n \"model.layers\": 12,\n \"model.hidden_size\": 1024,\n \"optimizer.lr\": 0.0005,\n \"batch_size\": 16\n })\n\n# Quick debugging setup\ndebug_training = base_training.override(\n epochs=1, batch_size=2, **{\"model.layers\": 1})\n\n# Now you can easily switch between configurations\n# TODO: Make this part of Configuronic\nif __name__ == \"__main__\":\n import sys\n configs = {\n 'dev': dev_training,\n 'prod': prod_training,\n 'experimental': experimental_training,\n 'debug': debug_training\n }\n\n config_name = sys.argv[1] if len(sys.argv) > 1 else 'dev'\n selected_config = configs.get(config_name, dev_training)\n\n cfn.cli(selected_config)\n```\n\n**Usage:**\n```bash\npython train.py dev # Use development config\npython train.py prod # Use production config\npython train.py experimental # Use experimental config\npython train.py debug # Use debug config\n\n# Still supports all override capabilities\npython train.py prod --epochs=50 --batch_size=128\n```\n\n\n## \ud83e\udd1d Contributing\nWe welcome contributions! Here's how to get started:\n\n### Development Setup\n```bash\ngit clone https://github.com/Positronic-Robotics/configuronic.git\ncd configuronic\nuv pip install -e \".[dev]\"\n```\n\n### Running Tests\n```bash\npytest # Run all tests\npytest --cov=configuronic # Run with coverage\n```\n### \ud83d\udccb Guidelines\n- Follow existing code style and patterns\n- Add tests for new functionality\n- Ensure all tests pass before submitting\n- Update documentation as needed\n\n## \ud83d\udcc4 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details.\n\n## \ud83d\udcde Support\n\n- \ud83d\udc1b **Bug Reports**: [GitHub Issues](https://github.com/Positronic-Robotics/configuronic/issues)\n- \ud83d\udcac **Discussions**: [GitHub Discussions](https://github.com/Positronic-Robotics/configuronic/discussions) **FIXME: Create Discord**\n- \ud83d\udce7 **Email**: hi@positronic.ro\n\n---\n\n**\u2b50 If you find Configuronic useful, please consider giving it a star on GitHub!**\n",
"bugtrack_url": null,
"license": "Copyright (c) 2024-2025 Positronic Robotics Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
"summary": "Simple yet powerful \"Configuration as Code\" library",
"version": "0.2.0",
"project_urls": {
"Homepage": "https://github.com/Positronic-Robotics/configuronic",
"Issues": "https://github.com/Positronic-Robotics/configuronic/issues",
"Repository": "https://github.com/Positronic-Robotics/configuronic"
},
"split_keywords": [
"configuration",
" config",
" dependency-injection",
" yaml"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "ab4c6fd7aeba337632085c677e93a5c667daa4f6c46e5e44cdfb9f9e9097f172",
"md5": "60397de186edd14bf1d348eb41d027b4",
"sha256": "ce2b0d5762cf76296b7c61b2f84c787446b805e73b1cc4886539696b1bab87b6"
},
"downloads": -1,
"filename": "configuronic-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "60397de186edd14bf1d348eb41d027b4",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 28963,
"upload_time": "2025-08-19T15:48:17",
"upload_time_iso_8601": "2025-08-19T15:48:17.660204Z",
"url": "https://files.pythonhosted.org/packages/ab/4c/6fd7aeba337632085c677e93a5c667daa4f6c46e5e44cdfb9f9e9097f172/configuronic-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-19 15:48:17",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Positronic-Robotics",
"github_project": "configuronic",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "configuronic"
}