Skip to main content

Configuration management system for complex systems

Project description

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 --help and 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 --flag syntax
  • Polymorphic configs - Runtime variant selection with type-safe inheritance
  • Nested configs - Compose complex configurations from smaller pieces
  • Container fields - List, Dict, and Mapping fields 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=True makes this the command used when none is specified
  • Base class fields (like verbose) are shared across all subcommands
  • Each subcommand gets its own --help showing 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 None default) or required (no default)
  • Positional args work with both BaseCommand subcommands 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
  • -h is reserved for --help and cannot be used as a shortcut
  • Shortcuts can be combined with regular --flags and 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

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

vidhi-0.0.9.tar.gz (144.5 kB view details)

Uploaded Source

Built Distribution

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

vidhi-0.0.9-py3-none-any.whl (62.8 kB view details)

Uploaded Python 3

File details

Details for the file vidhi-0.0.9.tar.gz.

File metadata

  • Download URL: vidhi-0.0.9.tar.gz
  • Upload date:
  • Size: 144.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for vidhi-0.0.9.tar.gz
Algorithm Hash digest
SHA256 73efb1d8cce96dd0182a03977de68c29bd143f324dc559ecc4fa8c8d0f8cd969
MD5 d2861a27007e301a8dd3cb61fb9d2c01
BLAKE2b-256 7623b48abd725c6a8faa504f1898467696cec8daa4277abe07c55e3d59f17bbd

See more details on using hashes here.

Provenance

The following attestation bundles were made for vidhi-0.0.9.tar.gz:

Publisher: publish.yml on project-vajra/vidhi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vidhi-0.0.9-py3-none-any.whl.

File metadata

  • Download URL: vidhi-0.0.9-py3-none-any.whl
  • Upload date:
  • Size: 62.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for vidhi-0.0.9-py3-none-any.whl
Algorithm Hash digest
SHA256 99c4ed45cdf25b3757cc9d0871c3898df70b5ed3ad0e043e4450da74e8c6bae5
MD5 49ff5a62125fe6a5e3f1504aad1adc66
BLAKE2b-256 49d25b0fb91f3eca69a27433805c5bad869fc49d5e8c76a063aca4cd85d4aba7

See more details on using hashes here.

Provenance

The following attestation bundles were made for vidhi-0.0.9-py3-none-any.whl:

Publisher: publish.yml on project-vajra/vidhi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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