Small Python CLI library
Project description
Kliamka
A small Python CLI library that provides Pydantic-based argument parser with type safety.
For detailed specifications, see CLAUDE.md.
Features
- 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
- Custom type converters — per-argument
converter=or a globalregister_converter(type, fn)registry - 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
Installation
pip install kliamka
Quick Start
from 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 3
API Reference
KliamkaArg
Descriptor 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
)
KliamkaArgClass
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")
ParserMeta
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")
@kliamka_cli
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:
pass
@kliamka_subcommands
Decorator 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})")
Feature Examples
Enum Arguments
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.
Environment Variable Fallback
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.
List Arguments
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 3
Positional Arguments
class MyArgs(KliamkaArgClass):
source: str = KliamkaArg("source", "Source file", positional=True)
dest: str = KliamkaArg("dest", "Destination file", positional=True)
Mutually Exclusive Arguments
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.
Custom Type Converters
kliamka has built-in support for bool, int, float, str, Enum, and List[...]. For anything else — pathlib.Path, datetime, uuid.UUID, custom classes — you can plug in a converter.
Two ways, first match wins:
1. Per-argument converter (explicit, scoped):
from pathlib import Path
from kliamka import KliamkaArg, KliamkaArgClass
class MyArgs(KliamkaArgClass):
config: Path = KliamkaArg(
"--config",
"Path to config file",
converter=lambda s: Path(s).expanduser().resolve(),
)
2. Global registry (reusable across all arguments of a given type):
from datetime import datetime
from kliamka import KliamkaArgClass, KliamkaArg, register_converter
register_converter(datetime, datetime.fromisoformat)
class MyArgs(KliamkaArgClass):
since: datetime = KliamkaArg("--since", "Start timestamp")
until: datetime = KliamkaArg("--until", "End timestamp")
Resolution order (first match wins, used by both CLI and env var parsing):
- Explicit
KliamkaArg(converter=...)on the field - A type registered via
register_converter(...) Enumsubclass — built-in enum parserList[T]— recursive lookup on element typeT- Fallback: the annotation itself (e.g.
int,float,str)
Converters can raise ValueError or TypeError on invalid input; kliamka wraps that into a clean CLI error like:
error: argument --port: invalid int value: '99999' (out of range: 99999)
Use unregister_converter(type) to remove a registered converter. See examples/custom_converters.py for a runnable demo.
Pydantic Validation
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
When validation fails during from_args(), kliamka raises KliamkaError with a simplified message. When validation fails inside @kliamka_cli or @kliamka_subcommands, the library renders that message through argparse, so users see standard CLI output such as:
error: Port must be 1-65535, got 99999
Documentation
CLAUDE.md— source of truth for project specificationsdocs/TODO.md— development log and task trackerdocs/20260408_product_requirements.md— current product requirements and roadmap baseline
Development
Requirements
- Python 3.11+
- Pydantic 2.0+
Setup
git clone https://github.com/hotsyk/kliamka.git
cd kliamka
make init-dev
make install
Available Make Commands
| Command | Description |
|---|---|
make install |
Install package in development mode |
make test |
Run unit tests with pytest (-m "not packaging") |
make test-package |
Build wheel/sdist and run packaging smoke tests |
make test-all |
Run the full pytest suite, including packaging smoke coverage |
make test-docker VERSION=3.11 |
Run tests in Docker for a specific Python version |
make test-docker-all |
Run Docker test matrix for Python 3.11–3.14 |
make lint |
Run type checking and linting |
make format |
Format code with ruff |
make clean |
Clean build artifacts |
Packaging and Compatibility Workflow
The repository now uses a modular package layout internally. The public import surface remains from kliamka import ..., while the active implementation lives under src/kliamka/ and the legacy top-level module path is kept as a compatibility facade.
Validation failures are also split more cleanly by context:
KliamkaArgClass.from_args()raisesKliamkaErrorwith simplified, joined validation messages for programmatic use.@kliamka_cliand@kliamka_subcommandshand those messages back toargparse, so CLI users still see standarderror:output.
GitHub Actions verifies both compatibility and packaging confidence on Python 3.11, 3.12, 3.13, and 3.14. The default local workflow mirrors that split:
make testfor unit coverage excluding packaging smoke markersmake test-packagefor built wheel/sdist installation smoke testsmake test-allfor the complete pytest suite
Docker Test Matrix
To mirror the pytest compatibility check locally across interpreter versions, use Dockerfile.test.
Run the full local Docker matrix:
make test-docker-all
This target runs four isolated Docker test jobs sequentially for Python 3.11, 3.12, 3.13, and 3.14.
Run one Dockerized test job for a specific Python version:
make test-docker VERSION=3.12
An optional docker-compose.test.yml file is included as a simple service definition reference for the supported Python versions.
These Docker targets bootstrap their own isolated environment inside the container and do not rely on the host .venv or on make test. Each container installs the package and runs:
pip install -e .
pip install pytest
pytest tests/ -v --tb=short
Success means the selected container exits cleanly and pytest passes for that Python version with no test failures. make test-docker-all is successful only when all four Python versions pass.
This intentionally keeps the Docker scope minimal and aligned with the CI pytest step, without pulling in linting, typing, or benchmark jobs.
Versions
See VERSIONS.md for detailed version history and changelog.
Examples
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 subcommandsexamples/custom_converters.py— Custom type converters (per-arg and global registry)
License
MIT-NORUS License — see LICENSE file for details.
Author
Volodymyr Hotsyk — https://github.com/hotsyk
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kliamka-0.6.0.tar.gz.
File metadata
- Download URL: kliamka-0.6.0.tar.gz
- Upload date:
- Size: 28.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0bdaf046680dcec384ace5c5ddb1f632de5ed9b8b0f76c7997a07589784611f
|
|
| MD5 |
57dc1d15818027083bea1ddac3ac8532
|
|
| BLAKE2b-256 |
8387fe6e817d1d5319ddaa6e38cd36070b982629be7e07a26601e4383165ca23
|
Provenance
The following attestation bundles were made for kliamka-0.6.0.tar.gz:
Publisher:
publish.yml on hotsyk/kliamka
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kliamka-0.6.0.tar.gz -
Subject digest:
f0bdaf046680dcec384ace5c5ddb1f632de5ed9b8b0f76c7997a07589784611f - Sigstore transparency entry: 1260639954
- Sigstore integration time:
-
Permalink:
hotsyk/kliamka@7f1727218c8deaa650ad5ba089ff3e8496de608d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/hotsyk
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7f1727218c8deaa650ad5ba089ff3e8496de608d -
Trigger Event:
workflow_run
-
Statement type:
File details
Details for the file kliamka-0.6.0-py3-none-any.whl.
File metadata
- Download URL: kliamka-0.6.0-py3-none-any.whl
- Upload date:
- Size: 16.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd1f98c00173fd2d9f943b53e35b3cf1369ff94f856f8296788ff4da2ea68ca7
|
|
| MD5 |
4ff81e9c172965c728876d144196d292
|
|
| BLAKE2b-256 |
e0ed37b7a50653045ee9300225b2c6033a02b50b7a40d7534152ebf3f8558087
|
Provenance
The following attestation bundles were made for kliamka-0.6.0-py3-none-any.whl:
Publisher:
publish.yml on hotsyk/kliamka
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kliamka-0.6.0-py3-none-any.whl -
Subject digest:
cd1f98c00173fd2d9f943b53e35b3cf1369ff94f856f8296788ff4da2ea68ca7 - Sigstore transparency entry: 1260639976
- Sigstore integration time:
-
Permalink:
hotsyk/kliamka@7f1727218c8deaa650ad5ba089ff3e8496de608d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/hotsyk
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7f1727218c8deaa650ad5ba089ff3e8496de608d -
Trigger Event:
workflow_run
-
Statement type: