Name | clypi JSON |
Version |
0.1.21
JSON |
| download |
home_page | None |
Summary | Your all-in-one for beautiful, lightweight, prod-ready CLIs |
upload_time | 2025-03-09 18:43:19 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.11 |
license | None |
keywords |
cli
terminal
ui
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# 🦄 clypi
[](https://badge.fury.io/py/clypi)
[](https://opensource.org/license/mit)
[](https://pypi.org/project/clypi/)
[](https://pypi.org/project/clypi/)
[](https://github.com/danimelchor/clypi/graphs/contributors)
Your all-in-one for beautiful, lightweight, prod-ready CLIs
#### Get started
```bash
uv add clypi # or `pip install clypi`
```
#### Examples
Check out the examples in `./examples`! You can run them locally with `uv run --all-extras -m examples.<example>`. E.g.:
```bash
uv run --all-extras -m examples.cli
# Or:
pip install .[examples]
python -m examples.cli
```
## Docs
Read [the API docs](https://github.com/danimelchor/clypi/blob/master/docs/index.md) for examples and a full API reference.
> [!IMPORTANT]
> This project is still in development. Expect frequent and (some) breaking changes. For upcoming
> releases, you can follow [the planned work section](https://github.com/danimelchor/clypi/blob/master/docs/planned_work.md).
## CLI
Read the [docs](https://github.com/danimelchor/clypi/blob/master/docs/index.md#cli)
```python
# examples/basic_cli.py
from clypi import Command, Positional, config
class Lint(Command):
files: Positional[tuple[str, ...]]
verbose = config(...) # 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 = config(
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()
```
<details open>
<summary><code>uv run -m examples.basic_cli lin</code> (Typo)</summary>
<p align="center">
<img width="1695" alt="image" src="https://github.com/user-attachments/assets/f57f6518-7d22-4320-a0fe-ec95c1c0579b" />
</p>
</details>
<details>
<summary><code>uv run -m examples.basic_cli -h</code> (Main help page)</summary>
<p align="center">
<img width="1692" alt="image" src="https://github.com/user-attachments/assets/cc939eab-c9db-4021-8374-a25b892a434c" />
</p>
</details>
<details>
<summary><code>uv run -m examples.basic_cli lint -h</code> (Subcommand help page)</summary>
<p align="center">
<img width="1692" alt="image" src="https://github.com/user-attachments/assets/52eb16a2-7edc-4563-ab3f-0bbe3ab05b14" />
</p>
</details>
<details>
<summary><code>uv run -m examples.basic_cli</code> (Normal run)</summary>
<p align="center">
<img width="836" alt="image" src="https://github.com/user-attachments/assets/030f4e2e-5046-4fa6-948a-c9ab80070ef7" />
</p>
</details>
<details>
<summary><code>uv run -m examples.basic_cli lint</code> (Missing args error)</summary>
<p align="center">
<img width="1692" alt="image" src="https://github.com/user-attachments/assets/4d42bed1-53a3-483f-8d34-fddb2ffec7c6" />
</p>
</details>
## 🌈 Colors
Read the [docs](https://github.com/danimelchor/clypi/blob/master/docs/index.md#colors)
```python
# 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"))
```
<details open>
<summary><code>uv run -m examples.colors</code></summary>
<p align="center">
<img width="974" alt="image" src="https://github.com/user-attachments/assets/9340d828-f7ce-491c-b0a8-6a666f7b7caf" />
</p>
</details>
<details>
<summary><code>uv run demo.py</code></summary>
<p align="center">
<img width="487" alt="image" src="https://github.com/user-attachments/assets/0ee3b49d-0358-4d8c-8704-2da89529b4f5" />
</p>
</details>
## 🌀 Spinners
Read the [docs](https://github.com/danimelchor/clypi/blob/master/docs/index.md#spinners)
```python
# demo.py
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())
```
<details open>
<summary><code>uv run -m examples.spinner</code></summary>
<p align="center">
<video src="https://github.com/user-attachments/assets/3af51391-1ab4-4b41-86f1-1e08e01be7b9" />
</p>
</details>
<details>
<summary><code>uv run demo.py</code></summary>
<p align="center">
<video src="https://github.com/user-attachments/assets/c0b4dc28-f6d4-4891-a9fa-be410119bd83" />
</p>
</details>
## ❓ Prompting
Read the [docs](https://github.com/danimelchor/clypi/blob/master/docs/index.md#prompt)
First, you'll need to import the `clypi` module:
```python
import clypi
answer = clypi.prompt("Are you going to use clypi?", default=True, parser=bool)
```
## 🔀 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:
```python
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](https://github.com/danimelchor/v6e), [pydantic](https://github.com/pydantic/pydantic), etc.)
CLIPy can be integrated with many parsers. The default recommended parser is [v6e](https://github.com/danimelchor/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**
```python
import v6e
from clypi import Command, config
class MyCli(Command):
files: list[Path] = config(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**
```python
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"
```
Raw data
{
"_id": null,
"home_page": null,
"name": "clypi",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": null,
"keywords": "cli, terminal, ui",
"author": null,
"author_email": "Daniel Melchor <dmelchor@pm.me>",
"download_url": "https://files.pythonhosted.org/packages/36/2b/594e50ae3643dcfb4d1441287e2f2d014665c2d4d5515a8d5d8e218071b4/clypi-0.1.21.tar.gz",
"platform": null,
"description": "# \ud83e\udd84 clypi\n\n[](https://badge.fury.io/py/clypi)\n[](https://opensource.org/license/mit)\n[](https://pypi.org/project/clypi/)\n[](https://pypi.org/project/clypi/)\n[](https://github.com/danimelchor/clypi/graphs/contributors)\n\nYour all-in-one for beautiful, lightweight, prod-ready CLIs\n\n#### Get started\n\n```bash\nuv add clypi # or `pip install clypi`\n```\n\n#### Examples\n\nCheck out the examples in `./examples`! You can run them locally with `uv run --all-extras -m examples.<example>`. E.g.:\n```bash\nuv run --all-extras -m examples.cli\n\n# Or:\npip install .[examples]\npython -m examples.cli\n```\n\n## Docs\n\nRead [the API docs](https://github.com/danimelchor/clypi/blob/master/docs/index.md) for examples and a full API reference.\n\n> [!IMPORTANT]\n> This project is still in development. Expect frequent and (some) breaking changes. For upcoming\n> releases, you can follow [the planned work section](https://github.com/danimelchor/clypi/blob/master/docs/planned_work.md).\n\n\n## CLI\n\nRead the [docs](https://github.com/danimelchor/clypi/blob/master/docs/index.md#cli)\n\n```python\n# examples/basic_cli.py\nfrom clypi import Command, Positional, config\n\nclass Lint(Command):\n files: Positional[tuple[str, ...]]\n verbose = config(...) # Comes from MyCli but I want to use it too\n\n async def run(self):\n print(f\"Linting {', '.join(self.files)} and {self.verbose=}\")\n\nclass MyCli(Command):\n \"\"\"\n my-cli is a very nifty demo CLI tool\n \"\"\"\n subcommand: Lint | None = None\n verbose: bool = config(\n help=\"Whether to show extra logs\",\n prompt=\"Do you want to see extra logs?\",\n default=False,\n short=\"v\", # User can pass in --verbose or -v\n )\n\n async def run(self):\n print(f\"Running the main command with {self.verbose}\")\n\nif __name__ == \"__main__\":\n cli: MyCli = MyCli.parse()\n cli.start()\n```\n\n<details open>\n <summary><code>uv run -m examples.basic_cli lin</code> (Typo)</summary>\n <p align=\"center\">\n <img width=\"1695\" alt=\"image\" src=\"https://github.com/user-attachments/assets/f57f6518-7d22-4320-a0fe-ec95c1c0579b\" />\n </p>\n</details>\n\n<details>\n <summary><code>uv run -m examples.basic_cli -h</code> (Main help page)</summary>\n <p align=\"center\">\n <img width=\"1692\" alt=\"image\" src=\"https://github.com/user-attachments/assets/cc939eab-c9db-4021-8374-a25b892a434c\" />\n </p>\n</details>\n\n<details>\n <summary><code>uv run -m examples.basic_cli lint -h</code> (Subcommand help page)</summary>\n <p align=\"center\">\n <img width=\"1692\" alt=\"image\" src=\"https://github.com/user-attachments/assets/52eb16a2-7edc-4563-ab3f-0bbe3ab05b14\" />\n </p>\n</details>\n\n<details>\n <summary><code>uv run -m examples.basic_cli</code> (Normal run)</summary>\n <p align=\"center\">\n <img width=\"836\" alt=\"image\" src=\"https://github.com/user-attachments/assets/030f4e2e-5046-4fa6-948a-c9ab80070ef7\" />\n </p>\n</details>\n\n<details>\n <summary><code>uv run -m examples.basic_cli lint</code> (Missing args error)</summary>\n <p align=\"center\">\n <img width=\"1692\" alt=\"image\" src=\"https://github.com/user-attachments/assets/4d42bed1-53a3-483f-8d34-fddb2ffec7c6\" />\n </p>\n</details>\n\n\n## \ud83c\udf08 Colors\n\nRead the [docs](https://github.com/danimelchor/clypi/blob/master/docs/index.md#colors)\n\n```python\n# demo.py\nimport clypi\n\n# Style text\nprint(clypi.style(\"This is blue\", fg=\"blue\"), \"and\", clypi.style(\"this is red\", fg=\"red\"))\n\n# Print with colors directly\nclypi.print(\"Some colorful text\", fg=\"green\", reverse=True, bold=True, italic=True)\n\n# Store a styler and reuse it\nwrong = clypi.styler(fg=\"red\", strikethrough=True)\nprint(\"The old version said\", wrong(\"Pluto was a planet\"))\nprint(\"The old version said\", wrong(\"the Earth was flat\"))\n```\n\n<details open>\n <summary><code>uv run -m examples.colors</code></summary>\n <p align=\"center\">\n <img width=\"974\" alt=\"image\" src=\"https://github.com/user-attachments/assets/9340d828-f7ce-491c-b0a8-6a666f7b7caf\" />\n </p>\n</details>\n\n\n<details>\n <summary><code>uv run demo.py</code></summary>\n <p align=\"center\">\n <img width=\"487\" alt=\"image\" src=\"https://github.com/user-attachments/assets/0ee3b49d-0358-4d8c-8704-2da89529b4f5\" />\n </p>\n</details>\n\n\n## \ud83c\udf00 Spinners\n\nRead the [docs](https://github.com/danimelchor/clypi/blob/master/docs/index.md#spinners)\n\n```python\n# demo.py\nimport asyncio\nfrom clypi import Spinner\n\nasync def main():\n async with Spinner(\"Downloading assets\") as s:\n for i in range(1, 6):\n await asyncio.sleep(0.5)\n s.title = f\"Downloading assets [{i}/5]\"\n\nasyncio.run(main())\n```\n<details open>\n <summary><code>uv run -m examples.spinner</code></summary>\n <p align=\"center\">\n <video src=\"https://github.com/user-attachments/assets/3af51391-1ab4-4b41-86f1-1e08e01be7b9\" />\n </p>\n</details>\n\n<details>\n <summary><code>uv run demo.py</code></summary>\n <p align=\"center\">\n <video src=\"https://github.com/user-attachments/assets/c0b4dc28-f6d4-4891-a9fa-be410119bd83\" />\n </p>\n</details>\n\n## \u2753 Prompting\n\nRead the [docs](https://github.com/danimelchor/clypi/blob/master/docs/index.md#prompt)\n\nFirst, you'll need to import the `clypi` module:\n```python\nimport clypi\n\nanswer = clypi.prompt(\"Are you going to use clypi?\", default=True, parser=bool)\n```\n\n## \ud83d\udd00 Async by default\n\n`clypi` was built with an async-first mentality. Asynchronous code execution is incredibly\nvaluable for applications like CLIs where we want to update the UI as we take certain actions behind the scenes.\nMost often, these actions can be made asynchronous since they involve things like file manipulation, network requests, subprocesses, etc.\n\n## \ud83d\udc0d Type-checking\n\nThis library is fully type-checked. This means that all types will be correctly inferred\nfrom the arguments you pass in.\n\nIn this example your editor will correctly infer the type:\n```python\nhours = clypi.prompt(\n \"How many hours are there in a year?\",\n parser=lambda x: float(x) if x < 24 else timedelta(days=x),\n)\nreveal_type(hours) # Type of \"res\" is \"float | timedelta\"\n```\n\n#### Why should I care?\n\nType checking will help you catch issues way earlier in the development cycle. It will also\nprovide nice autocomplete features in your editor that will make you faster \udb85\udc0b.\n\n## Integrations\n\n### Parsers ([v6e](https://github.com/danimelchor/v6e), [pydantic](https://github.com/pydantic/pydantic), etc.)\n\nCLIPy can be integrated with many parsers. The default recommended parser is [v6e](https://github.com/danimelchor/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:\n\n**CLI**\n```python\nimport v6e\nfrom clypi import Command, config\n\nclass MyCli(Command):\n files: list[Path] = config(parser=v6e.path().exists().list())\n\n async def run(self):\n files = [f.as_posix() for f in self.files]\n print(f\"Linting {', '.join(files)}\")\n\nif __name__ == \"__main__\":\n cli: MyCli = MyCli.parse()\n cli.start()\n```\n\n**Prompting**\n\n```python\nimport v6e\n\nhours = clypi.prompt(\n \"How many hours are there in a year?\",\n parser=v6e.float().lte(24).union(v6e.timedelta()),\n)\nreveal_type(hours) # Type of \"res\" is \"float | timedelta\"\n```\n",
"bugtrack_url": null,
"license": null,
"summary": "Your all-in-one for beautiful, lightweight, prod-ready CLIs",
"version": "0.1.21",
"project_urls": {
"Documentation": "https://github.com/danimelchor/clypi/blob/master/docs/index.md",
"Homepage": "https://github.com/danimelchor/clypi",
"Issues": "https://github.com/danimelchor/clypi/issues",
"Repository": "https://github.com/danimelchor/clypi"
},
"split_keywords": [
"cli",
" terminal",
" ui"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "b55664987a33f471ddcd242f3f917b8f6adc48198199c5d0acec423acfdae9d3",
"md5": "f0d53631a84ddf5c9af872e124ae4503",
"sha256": "a052e41a87af4b27c273c566ca5d5452644cdeb843dfa308e531fcf3bcdeafd4"
},
"downloads": -1,
"filename": "clypi-0.1.21-py3-none-any.whl",
"has_sig": false,
"md5_digest": "f0d53631a84ddf5c9af872e124ae4503",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 28780,
"upload_time": "2025-03-09T18:43:17",
"upload_time_iso_8601": "2025-03-09T18:43:17.778559Z",
"url": "https://files.pythonhosted.org/packages/b5/56/64987a33f471ddcd242f3f917b8f6adc48198199c5d0acec423acfdae9d3/clypi-0.1.21-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "362b594e50ae3643dcfb4d1441287e2f2d014665c2d4d5515a8d5d8e218071b4",
"md5": "b4565444b55f846e09dacd1a3e33957e",
"sha256": "f557e4826dd29e257ae07f82f9632a5d8ec27e5d41ff9cb42dad5e108a25e263"
},
"downloads": -1,
"filename": "clypi-0.1.21.tar.gz",
"has_sig": false,
"md5_digest": "b4565444b55f846e09dacd1a3e33957e",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 46254,
"upload_time": "2025-03-09T18:43:19",
"upload_time_iso_8601": "2025-03-09T18:43:19.556692Z",
"url": "https://files.pythonhosted.org/packages/36/2b/594e50ae3643dcfb4d1441287e2f2d014665c2d4d5515a8d5d8e218071b4/clypi-0.1.21.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-03-09 18:43:19",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "danimelchor",
"github_project": "clypi",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "clypi"
}