Vidhi
Vidhi (विधि, "method" in Sanskrit) is a Python configuration library for building type-safe, immutable configs with CLI and YAML support.
Features
- Immutable configs - Frozen dataclasses prevent accidental modifications
- CLI generation - Auto-generate
--helpand argument parsing from config classes - Subcommands - Build multi-command CLIs with
BaseCommand(names, aliases, defaults) - Shortcuts - Single-dash flags (
-r,-ld) that map to Enum values - Positional args - Accept values without
--flagsyntax - Polymorphic configs - Runtime variant selection with type-safe inheritance
- Nested configs - Compose complex configurations from smaller pieces
- Container fields -
List,Dict, andMappingfields with bracket-access CLI syntax - YAML loading - Load configs from files with
--config config.yaml - IDE autocomplete - Export JSON Schema for YAML file completion
- Shell completion - Tab completion for bash, zsh, and fish
Installation
pip install vidhi
Quick Start
Basic Config
from vidhi import frozen_dataclass, field, parse_cli_args
@frozen_dataclass
class TrainingConfig:
learning_rate: float = field(0.001, help="Learning rate", name="lr")
batch_size: int = field(32, help="Batch size")
epochs: int = field(10, help="Number of epochs")
config = parse_cli_args(TrainingConfig)
python train.py --help
python train.py --lr 0.01 --batch_size 64
python train.py --config config.yaml
Subcommands (BaseCommand)
Build multi-command CLIs where the first argument selects the command:
from vidhi import BaseCommand, field, frozen_dataclass, parse_cli_args
@frozen_dataclass
class MyTool(BaseCommand):
"""My Tool — a multi-command CLI"""
verbose: bool = field(False, help="Verbose output")
@frozen_dataclass
class Run(MyTool, name="run", alias="r", default=True):
"""Run a task"""
target: str = field(".", positional=True)
@frozen_dataclass
class List(MyTool, name="list", alias="l"):
"""List available tasks"""
cmd = parse_cli_args(MyTool)
mytool run ./src # explicit command + positional arg
mytool r ./src # alias
mytool --verbose # default command (Run) with base class flag
mytool list # different command
mytool --help # shows all commands with descriptions
mytool run --help # shows Run-specific flags
Key concepts:
name=registers the subcommand name (first positional arg)alias=registers a short alias (e.g.,"r"for"run")default=Truemakes this the command used when none is specified- Base class fields (like
verbose) are shared across all subcommands - Each subcommand gets its own
--helpshowing only its flags - Registries are per-base-class, so multiple tools don't conflict
Positional Args
Fields marked positional=True accept values without a --flag prefix:
@frozen_dataclass
class Grep(BaseCommand):
"""Search tool"""
@frozen_dataclass
class Search(Grep, name="search", alias="s", default=True):
"""Search for a pattern"""
pattern: str = field(None, positional=True)
ignore_case: bool = field(False, help="Case insensitive", name="i")
grep search mypattern # positional arg
grep s mypattern -i # alias + positional + shortcut
grep mypattern --ignore_case # default command + positional + flag
Rules:
- Only one field per config can be
positional=True - Positional args are optional (use
Nonedefault) or required (no default) - Positional args work with both
BaseCommandsubcommands and plain configs
Shortcuts
Map Enum values to single-dash flags for concise CLIs:
from enum import Enum
class Action(Enum):
RUN = "run"
LIST = "list"
LIST_DIR = "list-dir"
@frozen_dataclass
class Config:
action: Action = field(
Action.RUN,
shortcuts={Action.RUN: "r", Action.LIST: "l", Action.LIST_DIR: "ld"},
)
target: str = field(".", positional=True)
myapp -r # sets action=Action.RUN
myapp -l # sets action=Action.LIST
myapp -ld # multi-char shortcut, sets action=Action.LIST_DIR
myapp -r ./src # shortcut + positional arg
Rules:
- Keys must be Enum members, values are the flag strings (without
-) - Both single-char (
"r") and multi-char ("ld") flags are supported -his reserved for--helpand cannot be used as a shortcut- Shortcuts can be combined with regular
--flagsand positional args
Nested Configs
@frozen_dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
@frozen_dataclass
class AppConfig:
name: str = "app"
database: DatabaseConfig = field(default_factory=DatabaseConfig)
config = parse_cli_args(AppConfig)
python app.py --database.host db.example.com --database.port 3306
Container Fields (List, Dict, Mapping)
Use bracket-access syntax to set elements of List, Dict, and Mapping fields from the CLI:
from dataclasses import field as dc_field
from typing import Dict, List, Mapping
@frozen_dataclass
class GPULocation:
node_ip: str = "127.0.0.1"
device_id: int = 0
@frozen_dataclass
class ResourceAllocation:
gpus: List[GPULocation] = dc_field(default_factory=list)
@frozen_dataclass
class ServerConfig:
name: str = "server"
gpu_pools: Dict[str, List[GPULocation]] = dc_field(default_factory=dict)
resource_mapping: Mapping[int, ResourceAllocation] = dc_field(default_factory=dict)
config = parse_cli_args(ServerConfig)
List access — index with [N]:
python server.py --resource_mapping[0].gpus[0].node_ip 10.0.0.1 \
--resource_mapping[0].gpus[0].device_id 0 \
--resource_mapping[0].gpus[1].node_ip 10.0.0.2
Dict access — index with [key]:
python server.py --gpu_pools[pool_a][0].node_ip 10.0.0.1 \
--gpu_pools[pool_a][0].device_id 0 \
--gpu_pools[pool_b][0].node_ip 10.0.0.3
Mapping access — index with [key] (key type is auto-coerced):
python server.py --resource_mapping[0].gpus[0].node_ip 10.0.0.1 \
--resource_mapping[1].gpus[0].node_ip 10.0.0.2
Container fields also work in YAML files:
# config.yaml
gpu_pools:
pool_a:
- node_ip: "10.0.0.1"
device_id: 0
pool_b:
- node_ip: "10.0.0.3"
device_id: 1
resource_mapping:
0:
gpus:
- node_ip: "10.0.0.1"
device_id: 0
python server.py --config config.yaml
# CLI overrides work with container fields too:
python server.py --config config.yaml --resource_mapping[0].gpus[0].device_id 4
zsh users: Quote bracket args with single quotes to avoid glob expansion:
'--resource_mapping[0].gpus[0].node_ip' 10.0.0.1
Polymorphic Configs
from enum import Enum
from vidhi import BasePolyConfig, frozen_dataclass, field
class CacheType(Enum):
MEMORY = "memory"
REDIS = "redis"
@frozen_dataclass
class BaseCacheConfig(BasePolyConfig):
ttl: int = 3600
@classmethod
def get_type(cls) -> CacheType:
raise NotImplementedError()
@frozen_dataclass
class MemoryCacheConfig(BaseCacheConfig):
max_size: int = 1000
@classmethod
def get_type(cls) -> CacheType:
return CacheType.MEMORY
@frozen_dataclass
class RedisCacheConfig(BaseCacheConfig):
host: str = "localhost"
port: int = 6379
@classmethod
def get_type(cls) -> CacheType:
return CacheType.REDIS
@frozen_dataclass
class AppConfig:
cache: BaseCacheConfig = field(default_factory=MemoryCacheConfig)
config = parse_cli_args(AppConfig)
python app.py --cache_type redis --cache.host redis.example.com
python app.py --cache_type memory --cache.max_size 5000
YAML Loading
# config.yaml
cache_type: redis
cache:
host: redis.example.com
port: 6379
ttl: 7200
python app.py --config config.yaml
python app.py --config config.yaml --cache.ttl 3600 # CLI overrides YAML
CLI Features
Every Vidhi config automatically supports:
| Flag | Description |
|---|---|
--help |
Show help with all options organized by variant |
--config FILE |
Load configuration from YAML file |
--export-json-schema [FILE] |
Export JSON Schema for IDE autocomplete |
--install-shell-completions [SHELL] |
Install tab completion (bash/zsh/fish) |
API Summary
| Function | Description |
|---|---|
@frozen_dataclass |
Decorator for immutable config classes |
field(default, help=, name=, positional=, shortcuts=) |
Field with CLI metadata |
parse_cli_args(cls) |
Parse CLI into config (supports both flat configs and BaseCommand) |
with_cli_overrides(config) |
Override existing config from CLI |
load_yaml_config(path) |
Load YAML to dict |
create_class_from_dict(cls, dict) |
Create config from dict |
dataclass_to_dict(config) |
Serialize config to dict |
BasePolyConfig |
Base class for polymorphic configs |
BaseCommand |
Base class for subcommand CLIs (name, alias, default) |
Documentation
Full documentation: https://project-vajra.github.io/vidhi
See examples/ for runnable code samples.
License
Apache License 2.0
Metadata
Release files for vidhi 0.0.9
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vidhi-0.0.9.tar.gz | 144.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vidhi-0.0.9-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 207.2 kB
Release files / vidhi-0.0.9.tar.gz
| Download URL | vidhi-0.0.9.tar.gz |
|---|---|
| Size | 144.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
73efb1d8cce96dd0182a03977de68c29bd143f324dc559ecc4fa8c8d0f8cd969
|
|
BLAKE2b-256 checksum How to use checksums |
7623b48abd725c6a8faa504f1898467696cec8daa4277abe07c55e3d59f17bbd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Mar 8, 2026.
Transparency logRelease files / vidhi-0.0.9-py3-none-any.whl
| Download URL | vidhi-0.0.9-py3-none-any.whl |
|---|---|
| Size | 62.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
99c4ed45cdf25b3757cc9d0871c3898df70b5ed3ad0e043e4450da74e8c6bae5
|
|
BLAKE2b-256 checksum How to use checksums |
49d25b0fb91f3eca69a27433805c5bad869fc49d5e8c76a063aca4cd85d4aba7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Mar 8, 2026.
Transparency log