Skip to main content

Your all-in-one for beautiful, lightweight, prod-ready CLIs

Project description

🦄 clypi

PyPI version License PyPI - Python Version PyPI - Downloads Contributors

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

image

🛠️ 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

image

🌈 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

image

🌀 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

clypi-1.0.4.tar.gz (51.8 kB view details)

Uploaded Source

Built Distribution

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

clypi-1.0.4-py3-none-any.whl (32.4 kB view details)

Uploaded Python 3

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

Hashes for clypi-1.0.4.tar.gz
Algorithm Hash digest
SHA256 bdd9c2ad1da5a49d97b19ec5d50e0e6c26b6d64ec734e3bc78ffa944472bcd33
MD5 b2467a0358b68744dfc6c9b314f83569
BLAKE2b-256 bcdc82e1360662af28e5355ba7e6495bb0d07e4c1317b7b277f685fa216f2e16

See more details on using hashes here.

Provenance

The following attestation bundles were made for clypi-1.0.4.tar.gz:

Publisher: release.yml on danimelchor/clypi

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

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

Hashes for clypi-1.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 33afa7b11c080a8fe9bca589d4e9b9edf0e37b472be0c3911925b3bfd99d2ca7
MD5 2156aba6182ec844d9949d23bb142087
BLAKE2b-256 b340144ecb556cd55014e7ba9e1986ec07fa95c7df021829076f291acb8ee79e

See more details on using hashes here.

Provenance

The following attestation bundles were made for clypi-1.0.4-py3-none-any.whl:

Publisher: release.yml on danimelchor/clypi

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