A cli tool based on Pydantic models.
Project description
Clipstick
A cli-tool based on Pydantic models.
There are many other tools out there that do kind of the same, but they all don't do quite exactly what I want.
The goal of clipstip is to use pydantic to model your cli by leveraging:
- The automatic casting of input variables.
- The powerful validation capabilities.
- Docstrings as cli documentation.
- No other mental model required than Typing and Pydantic.
Clipstick is inspired by tyro. It is excellent and is more versatile than this tool. But in my opionion its primary focus is not building a cli tool along the lines of Argparse or Click, but more on composing complex objects from the command line. Making tyro behave like a "traditional" cli requires additional Annotation
flags, which I don't want.
Installation
pip install clipstick
Example
# examples/simple.py
from pydantic import BaseModel
from clipstick import parse
class SimpleModel(BaseModel):
"""A simple model demonstrating clipstick.
This is used in help as describing the main command.
"""
name: str
"""Your name. This is used in help describing name."""
repeat_count: int = 10
"""How many times to repeat your name. Used in help describing repeat_count."""
def main(self):
for _ in range(self.repeat_count):
print(f"hello: {self.name}")
if __name__ == "__main__":
model = parse(SimpleModel)
model.main()
python examples/simple.py -h
gives you:
usage: <your entrypoint here> [-h] name [['--repeat-count']]
A simple model demonstrating clipstick.
This is used in help as describing the main command.
positional arguments:
name Your name. This is used in help describing name. [str]
optional keyword arguments:
--repeat-count How many times to repeat your name. Used in help describing repeat_count. [int]
python examples/simple.py alex --repeat-count 3
gives you:
hello: alex
hello: alex
hello: alex
[!NOTE] The inclusion of the
def main(self)
method is not a requirement.clipstick
generates a pydantic model based on provided cli arguments and gives it back to you for your further usage. Usingdef main()
is one of the options to further process it.
Positional arguments
All properties in your pydantic model without a default value are converted to cli positional arguments.
# source/positional_arg.py
from pydantic import BaseModel
from clipstick import parse
class MyModel(BaseModel):
my_value: int
if __name__ == "__main__":
"""your cli entrypoint"""
model = parse(MyModel)
# >>> python source/positional_arg.py 10
MyModel(my_value=10)
# >>> python source/positional_arg.py -h
usage: <your entrypoint here> [-h] my-value
None
positional arguments:
my-value None [int]
Keyword arguments
All fields with a default value are converted to cli optional arguments.
# source/keyword_arg.py
from pydantic import BaseModel
from clipstick import parse
class MyModel(BaseModel):
my_value: int = 22
if __name__ == "__main__":
model = parse(MyModel)
# >>> python source/keyword_arg.py --my-value 10
MyModel(my_value=10)
# >>> python source/keyword_arg.py -h
usage: <your entrypoint here> [-h] [['--my-value']]
None
optional keyword arguments:
--my-value None [int]
Choices
Lists
Booleans/Flags
# source/boolean_required_arg.py
from pydantic import BaseModel
from clipstick import parse
class MyModel(BaseModel):
verbose: bool
if __name__ == "__main__":
model = parse(MyModel)
# >>> python source/boolean_required_arg.py --verbose
MyModel(verbose=True)
# >>> python source/boolean_required_arg.py --no-verbose
MyModel(verbose=False)
# >>> python source/boolean_required_arg.py -h
usage: <your entrypoint here> [-h] --verbose
None
positional arguments:
--verbose/--no-verbose None [bool]
Subcommands
Subcommands are possible by adding a property with a union of BaseModel
, each defined as new path in the sub-command tree.
# source/subcommand_arg.py
from pydantic import BaseModel
from clipstick import parse
class Routes(BaseModel):
route_name: str
class Climbers(BaseModel):
climber_name: str
class MyModel(BaseModel):
"""The base model with a subcommand."""
sub_command: Routes | Climbers
if __name__ == "__main__":
model = parse(MyModel)
# >>> python source/subcommand_arg.py climbers Ondra
MyModel(sub_command=Climbers(climber_name='Ondra'))
# >>> python source/subcommand_arg.py -h
usage: <your entrypoint here> [-h] {routes, climbers}
The base model with a subcommand.
subcommands:
routes None
climbers None
- Only one subcommand per model is allowed. (If you need more (and want to follow the more object-composition path), have a look at tyro)
sub_command
as a name is not required. Any name will do.- Nesting of subcommands is possible.
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
Hashes for clipstick-0.1.3-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ec89e48d320ea601ef6967f5b132663b9ac84857531274dd38566efe4a1411d3 |
|
MD5 | a553bb0f72aee98a93dd0c1ec24d2c11 |
|
BLAKE2b-256 | b98ca4f18d6569860cc2f4c370033c547082d8f0eb1527513b818f014e5fceea |