A small Python CLI library that provides Pydantic-based argument parser with type safety.
- Type-safe CLI arguments with Pydantic validation
- Decorator-based design for clean, readable code
- Automatic argument parsing from class definitions
- Enum support — string and integer valued enums, parsed by name or value
- Positional arguments — with optional defaults
- List/array arguments —
List[str],List[int],List[Enum] - Environment variable fallback — CLI > ENV > default priority
- Subcommands — git-style CLI with
@kliamka_subcommands - Short flags —
-valongside--verbose - Mutually exclusive groups — e.g.
--jsonvs--csv --versionflag — automatic version display- Help customization — program name, usage, epilog
- Pydantic validators — range checks, cross-field validation, regex patterns
- Programmatic argv — pass custom argument lists for testing/embedding
- PEP 561 compatible — ships
py.typedmarker - Modern Python 3.11+ with full type hints
pip install kliamkafrom kliamka import KliamkaArg, KliamkaArgClass, kliamka_cli
class MyArgs(KliamkaArgClass):
"""My CLI application arguments."""
verbose: bool | None = KliamkaArg("--verbose", "Enable verbose output", short="-v")
count: int | None = KliamkaArg("--count", "Number of iterations", default=1, short="-c")
@kliamka_cli(MyArgs)
def main(args: MyArgs) -> None:
if args.verbose:
print("Verbose mode enabled")
for i in range(args.count or 1):
print(f"Iteration {i + 1}")
if __name__ == "__main__":
main()python my_app.py -v -c 3Descriptor for defining CLI arguments.
KliamkaArg(
flag: str, # Flag name: "--verbose" or "filename"
help_text: str = "", # Help text
default: Any = None, # Default value
positional: bool = False, # Positional argument
env: str | None = None, # Environment variable fallback
short: str | None = None, # Short flag: "-v"
mutually_exclusive: str | None = None, # Exclusion group name
)Base class for CLI argument definitions using Pydantic models.
class MyArgs(KliamkaArgClass):
"""Description shown in --help."""
debug: bool | None = KliamkaArg("--debug", "Enable debug mode", short="-d")
config: str | None = KliamkaArg("--config", "Config file path")Customize help output and add --version:
from kliamka import ParserMeta
class MyArgs(KliamkaArgClass):
"""My application."""
parser_meta = ParserMeta(
prog="myapp",
usage="myapp [options] FILE",
epilog="See https://example.com for docs.",
version="myapp 1.0.0",
)
verbose: bool | None = KliamkaArg("--verbose", "Verbose", short="-v")Decorator that parses CLI arguments and injects them as the first parameter.
@kliamka_cli(MyArgs)
def main(args: MyArgs) -> None:
pass
# Or with custom argv for testing:
@kliamka_cli(MyArgs, argv=["--verbose", "--count", "5"])
def main(args: MyArgs) -> None:
passDecorator for git-style subcommand CLIs.
from kliamka import kliamka_subcommands
class MainArgs(KliamkaArgClass):
verbose: bool | None = KliamkaArg("--verbose", "Verbose output", short="-v")
class AddArgs(KliamkaArgClass):
"""Add a new item."""
name: str = KliamkaArg("name", "Item name", positional=True)
class RemoveArgs(KliamkaArgClass):
"""Remove an item."""
id: int = KliamkaArg("id", "Item ID", positional=True)
force: bool | None = KliamkaArg("--force", "Force removal", short="-f")
@kliamka_subcommands(MainArgs, {"add": AddArgs, "remove": RemoveArgs})
def main(args: MainArgs, command: str, cmd_args) -> None:
if command == "add":
print(f"Adding {cmd_args.name}")
elif command == "remove":
print(f"Removing {cmd_args.id} (force={cmd_args.force})")from enum import Enum
class LogLevel(Enum):
DEBUG = "debug"
INFO = "info"
ERROR = "error"
class MyArgs(KliamkaArgClass):
level: LogLevel = KliamkaArg("--level", "Log level", LogLevel.INFO)Accepts --level debug, --level DEBUG, or --level info.
class MyArgs(KliamkaArgClass):
api_key: str | None = KliamkaArg("--api-key", "API key", env="MY_API_KEY")
debug: bool | None = KliamkaArg("--debug", "Debug mode", env="DEBUG")Priority: CLI argument > environment variable > default value.
from typing import List
class MyArgs(KliamkaArgClass):
files: List[str] = KliamkaArg("--files", "Input files")
counts: List[int] = KliamkaArg("--counts", "Counts")python app.py --files a.txt b.txt --counts 1 2 3class MyArgs(KliamkaArgClass):
source: str = KliamkaArg("source", "Source file", positional=True)
dest: str = KliamkaArg("dest", "Destination file", positional=True)class MyArgs(KliamkaArgClass):
json_out: bool | None = KliamkaArg(
"--json", "JSON output", mutually_exclusive="format"
)
csv_out: bool | None = KliamkaArg(
"--csv", "CSV output", mutually_exclusive="format"
)--json and --csv cannot be used together.
from pydantic import model_validator
class MyArgs(KliamkaArgClass):
port: int | None = KliamkaArg("--port", "Port number", default=8080)
@model_validator(mode="after")
def validate_port(self) -> "MyArgs":
if self.port is not None and not (1 <= self.port <= 65535):
raise ValueError(f"Port must be 1-65535, got {self.port}")
return self- Python 3.11+
- Pydantic 2.0+
git clone https://github.com/hotsyk/kliamka.git
cd kliamka
make init-dev
make install| Command | Description |
|---|---|
make install |
Install package in development mode |
make test |
Run tests with pytest |
make lint |
Run type checking and linting |
make format |
Format code with ruff |
make clean |
Clean build artifacts |
See VERSIONS.md for detailed version history and changelog.
See the examples/ directory:
examples/basic_usage.py— Basic CLI argument handlingexamples/enums.py— Enum typesexamples/positional_args.py— Positional argumentsexamples/list_args.py— List argumentsexamples/env_vars.py— Environment variable fallbackexamples/subcommands.py— Git-style subcommands
MIT-NORUS License — see LICENSE file for details.
Volodymyr Hotsyk — https://github.com/hotsyk