Your all-in-one for beautiful, lightweight, prod-ready CLIs
Project description
🦄 clypi
Your all-in-one for beautiful, lightweight, prod-ready CLIs
Get started
uv add clypi # or `pip install clypi`
Examples
You can run the examples in ./examples locally. First, clone the repository, then use uv run --all-extras -m examples.<example>. E.g.:
uv run --all-extras -m examples.cli
# Or:
pip install .[examples]
python -m examples.cli
📖 Docs
Read the API docs for examples and a full API reference.
🧰 CLI
Read the docs
# examples/basic_cli.py
from clypi import Command, Positional, arg
class Lint(Command):
files: Positional[tuple[str, ...]]
verbose: bool = arg(...) # Comes from MyCli but I want to use it too
async def run(self):
print(f"Linting {', '.join(self.files)} and {self.verbose=}")
class MyCli(Command):
"""
my-cli is a very nifty demo CLI tool
"""
subcommand: Lint | None = None
verbose: bool = arg(
help="Whether to show extra logs",
prompt="Do you want to see extra logs?",
default=False,
short="v", # User can pass in --verbose or -v
)
async def run(self):
print(f"Running the main command with {self.verbose}")
if __name__ == "__main__":
cli: MyCli = MyCli.parse()
cli.start()
uv run -m examples.basic_cli lin
🛠️ Configurable
Read the docs
Clypi lets you configure the app globally. This means that all the styling will be easy, uniform across your entire app, and incredibly maintainable.
For example, this is how you'd achieve a UI like uv's CLI:
from clypi import ClypiConfig, ClypiFormatter, Styler, Theme, configure
configure(
ClypiConfig(
theme=Theme(
usage=Styler(fg="green", bold=True),
prog=Styler(fg="cyan", bold=True),
section_title=Styler(fg="green", bold=True),
subcommand=Styler(fg="cyan"),
long_option=Styler(fg="cyan"),
short_option=Styler(fg="cyan"),
positional=Styler(fg="cyan"),
type_str=Styler(fg="cyan"),
prompts=Styler(fg="green", bold=True),
),
help_formatter=ClypiFormatter(boxed=False),
)
)
uv run -m examples.uv ad
🌈 Colors
Read the docs
# demo.py
import clypi
# Style text
print(clypi.style("This is blue", fg="blue"), "and", clypi.style("this is red", fg="red"))
# Print with colors directly
clypi.print("Some colorful text", fg="green", reverse=True, bold=True, italic=True)
# Store a styler and reuse it
wrong = clypi.Styler(fg="red", strikethrough=True)
print("The old version said", wrong("Pluto was a planet"))
print("The old version said", wrong("the Earth was flat"))
uv run -m examples.colors
🌀 Spinners
Read the docs
You can use spinners as an async context manager:
import asyncio
from clypi import Spinner
async def main():
async with Spinner("Downloading assets") as s:
for i in range(1, 6):
await asyncio.sleep(0.5)
s.title = f"Downloading assets [{i}/5]"
asyncio.run(main())
Or as a decorator:
import asyncio
from clypi import spinner
@spinner("Doing work", capture=True)
async def do_some_work():
await asyncio.sleep(5)
asyncio.run(do_some_work())
uv run -m examples.spinner
https://github.com/user-attachments/assets/2065b3dd-c73c-4e21-b698-8bf853e8e520
❓ Prompting
Read the docs
First, you'll need to import the clypi module:
import clypi
answer = clypi.confirm("Are you going to use clypi?", default=True)
🔀 Async by default
clypi was built with an async-first mentality. Asynchronous code execution is incredibly
valuable for applications like CLIs where we want to update the UI as we take certain actions behind the scenes.
Most often, these actions can be made asynchronous since they involve things like file manipulation, network requests, subprocesses, etc.
🐍 Type-checking
This library is fully type-checked. This means that all types will be correctly inferred from the arguments you pass in.
In this example your editor will correctly infer the type:
hours = clypi.prompt(
"How many hours are there in a year?",
parser=lambda x: float(x) if x < 24 else timedelta(days=x),
)
reveal_type(hours) # Type of "res" is "float | timedelta"
Why should I care?
Type checking will help you catch issues way earlier in the development cycle. It will also provide nice autocomplete features in your editor that will make you faster .
🔌 Integrations
Parsers (v6e, pydantic, etc.)
clypi can be integrated with many parsers. The default recommended parser is v6e, which is automatically used if installed in your local environment to parse types more accurately. If you wish you specify any parser (from v6e or elsewhere) manually, you can do so quite easily:
CLI
import v6e
from clypi import Command, arg
class MyCli(Command):
files: list[Path] = arg(parser=v6e.path().exists().list())
async def run(self):
files = [f.as_posix() for f in self.files]
print(f"Linting {', '.join(files)}")
if __name__ == "__main__":
cli: MyCli = MyCli.parse()
cli.start()
Prompting
import v6e
hours = clypi.prompt(
"How many hours are there in a year?",
parser=v6e.float().lte(24).union(v6e.timedelta()),
)
reveal_type(hours) # Type of "res" is "float | timedelta"
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 clypi-1.0.4.tar.gz.
File metadata
- Download URL: clypi-1.0.4.tar.gz
- Upload date:
- Size: 51.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bdd9c2ad1da5a49d97b19ec5d50e0e6c26b6d64ec734e3bc78ffa944472bcd33
|
|
| MD5 |
b2467a0358b68744dfc6c9b314f83569
|
|
| BLAKE2b-256 |
bcdc82e1360662af28e5355ba7e6495bb0d07e4c1317b7b277f685fa216f2e16
|
Provenance
The following attestation bundles were made for clypi-1.0.4.tar.gz:
Publisher:
release.yml on danimelchor/clypi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clypi-1.0.4.tar.gz -
Subject digest:
bdd9c2ad1da5a49d97b19ec5d50e0e6c26b6d64ec734e3bc78ffa944472bcd33 - Sigstore transparency entry: 179562731
- Sigstore integration time:
-
Permalink:
danimelchor/clypi@576eb68d2eb25675f8c5b09f856531eaf9e6f5f5 -
Branch / Tag:
refs/tags/1.0.4 - Owner: https://github.com/danimelchor
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@576eb68d2eb25675f8c5b09f856531eaf9e6f5f5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file clypi-1.0.4-py3-none-any.whl.
File metadata
- Download URL: clypi-1.0.4-py3-none-any.whl
- Upload date:
- Size: 32.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
33afa7b11c080a8fe9bca589d4e9b9edf0e37b472be0c3911925b3bfd99d2ca7
|
|
| MD5 |
2156aba6182ec844d9949d23bb142087
|
|
| BLAKE2b-256 |
b340144ecb556cd55014e7ba9e1986ec07fa95c7df021829076f291acb8ee79e
|
Provenance
The following attestation bundles were made for clypi-1.0.4-py3-none-any.whl:
Publisher:
release.yml on danimelchor/clypi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clypi-1.0.4-py3-none-any.whl -
Subject digest:
33afa7b11c080a8fe9bca589d4e9b9edf0e37b472be0c3911925b3bfd99d2ca7 - Sigstore transparency entry: 179562733
- Sigstore integration time:
-
Permalink:
danimelchor/clypi@576eb68d2eb25675f8c5b09f856531eaf9e6f5f5 -
Branch / Tag:
refs/tags/1.0.4 - Owner: https://github.com/danimelchor
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@576eb68d2eb25675f8c5b09f856531eaf9e6f5f5 -
Trigger Event:
push
-
Statement type: