SkillAgentSearch skills...

Feud

Build powerful CLIs with simple idiomatic Python, driven by type hints. Not all arguments are bad.

Install / Use

/learn @eonu/Feud
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<p align="center"> <h1 align="center"> Feud </h1> <p align="center"><b>Not all arguments are bad.</b></p> </p> <img src="https://raw.githubusercontent.com/eonu/feud/master/docs/source/_static/images/logo/logo.png" align="right" width="100px"> <p align="center"> <em>Build powerful CLIs with simple idiomatic Python, driven by type hints.</em> </p> <p align="center"> <div align="center"> <a href="https://pypi.org/project/feud"> <img src="https://img.shields.io/pypi/v/feud?logo=pypi&style=flat-square" alt="PyPI"/> </a> <a href="https://pypi.org/project/feud"> <img src="https://img.shields.io/pypi/pyversions/feud?logo=python&style=flat-square" alt="PyPI - Python Version"/> </a> <a href="https://feud.readthedocs.io/en/latest"> <img src="https://img.shields.io/readthedocs/feud.svg?logo=read-the-docs&style=flat-square" alt="Read The Docs - Documentation"/> </a> <a href="https://coveralls.io/github/eonu/feud"> <img src="https://img.shields.io/coverallsCoverage/github/eonu/feud?logo=coveralls&style=flat-square" alt="Coveralls - Coverage"/> </a> <a href="https://raw.githubusercontent.com/eonu/feud/master/LICENSE"> <img src="https://img.shields.io/pypi/l/feud?style=flat-square" alt="PyPI - License"/> </a> </div> </p> <p align="center"> <sup> <a href="#about">About</a> · <a href="#features">Features</a> · <a href="#installation">Installation</a> · <a href="#build-status">Build status</a> · <a href="#documentation">Documentation</a> · <a href="#related-projects">Related projects</a> · <a href="#contributing">Contributing</a> · <a href="#licensing">Licensing</a> </sup> </p>

About

Designing a good CLI can quickly spiral into chaos without the help of an intuitive CLI framework.

Feud builds on Click for argument parsing, along with Pydantic for typing, to make CLI building a breeze.

Features

Simplicity

Click is often considered the defacto command-line building utility for Python – offering far more functionality and better ease-of-use than the standard library's argparse. Despite this, for even the simplest of CLIs, code written using Click can be somewhat verbose and often requires frequently looking up documentation.

Consider the following example command for serving local files on a HTTP server.

In red is a typical Click implementation, and in green is the Feud equivalent.

<table> <tr> <td>

Example: Command for running a HTTP web server.

</td> </tr> <tr> <td>
# serve.py

- import click
+ import feud
+ from typing import Literal

- @click.command
- @click.argument("port", type=int, help="Server port.")
- @click.option("--watch/--no-watch", type=bool, default=True, help="Watch source code for changes.")
- @click.option("--env", type=click.Choice(["dev", "prod"]), default="dev", help="Environment mode.")
- def serve(port, watch, env):
+ def serve(port: int, *, watch: bool = True, env: Literal["dev", "prod"] = "dev"):
-     """Start a local HTTP server."""
+     """Start a local HTTP server.
+
+     Parameters
+     ----------
+     port:
+         Server port.
+     watch:
+         Watch source code for changes.
+     env:
+         Environment mode.
+     """

if __name__ == "__main__":
-     serve()
+     feud.run(serve)
</td> </tr> <tr> <td> <details> <summary> <b>Click here to view the generated help screen.</b> </summary> <p>

Help screen for the serve command.

$ python serve.py --help

 Usage: serve.py [OPTIONS] PORT

 Start a local HTTP server.

╭─ Arguments ────────────────────────────────────────────────────────╮
│ *  PORT    INTEGER  [required]                                     │
╰────────────────────────────────────────────────────────────────────╯
╭─ Options ──────────────────────────────────────────────────────────╮
│ --watch/--no-watch                Watch source code for changes.   │
│                                   [default: watch]                 │
│ --env                 [dev|prod]  Environment mode. [default: dev] │
│ --help                            Show this message and exit.      │
╰────────────────────────────────────────────────────────────────────╯
</p> </details> </td> </tr> <tr> <td> <details> <summary> <b>Click here to see usage examples.</b> </summary> <p>
  • python serve.py 8080
  • python serve.py 3000 --watch --env dev
  • python serve.py 4567 --no-watch --env prod
</p> </details> </td> </tr> </table>

The core design principle behind Feud is to make it as easy as possible for even beginner Python developers to quickly create sophisticated CLIs.

The above function is written in idiomatic Python, adhering to language standards and using basic core language features such as type hints and docstrings to declare the relevant information about the CLI, but relying on Feud to carry out the heavy lifting of converting these language elements into a fully-fledged CLI.

Grouping commands

While a single command is often all that you need, Feud makes it straightforward to logically group together related commands into a group represented by a class with commands defined within it.

<table> <tr> <td>

Example: Commands for creating, deleting and listing blog posts.

</td> </tr> <tr> <td>
# post.py

import feud
from datetime import date

class Post(feud.Group):
    """Manage blog posts."""

    def create(id: int, *, title: str, desc: str | None = None):
        """Create a blog post."""

    def delete(*ids: int):
        """Delete blog posts."""

    def list(*, between: tuple[date, date] | None = None):
        """View all blog posts, optionally filtering by date range."""

if __name__ == "__main__":
    feud.run(Post)
</td> </tr> <tr> <td> <details> <summary> <b>Click here to view the generated help screen.</b> </summary> <p>

Help screen for the post group.

$ python post.py --help

 Usage: post.py [OPTIONS] COMMAND [ARGS]...

 Manage blog posts.

╭─ Options ──────────────────────────────────────────────────────────╮
│ --help      Show this message and exit.                            │
╰────────────────────────────────────────────────────────────────────╯
╭─ Commands ─────────────────────────────────────────────────────────╮
│ create   Create a blog post.                                       │
│ delete   Delete blog posts.                                        │
│ list     View all blog posts, optionally filtering by date range.  │
╰────────────────────────────────────────────────────────────────────╯

Help screen for the list command within the post group.

$ python post.py list --help

 Usage: post.py list [OPTIONS]

 View all blog posts, optionally filtering by date range.

╭─ Options ──────────────────────────────────────────────────────────╮
│ --between    <DATE DATE>...                                        │
│ --help                       Show this message and exit.           │
╰────────────────────────────────────────────────────────────────────╯
</p> </details> </td> </tr> <tr> <td> <details> <summary> <b>Click here to see usage examples.</b> </summary> <p>
  • python post.py create 1 --title "My First Post"
  • python post.py create 2 --title "My First Detailed Post" --desc "Hi!"
  • python post.py delete 1 2
  • python post.py list
  • python post.py list --between 2020-01-30 2021-01-30
</p> </details> </td> </tr> </table>

Alternatively, if you already have some functions defined that you would like to run as commands, you can simply provide them to feud.run and it will automatically generate and run a group with those commands.

# post.py

import feud
from datetime import date

def create_post(id: int, *, title: str, desc: str | None = None):
    """Create a blog post."""

def delete_posts(*ids: int):
    """Delete blog posts."""

def list_posts(*, between: tuple[date, date] | None = None):
    """View all blog posts, optionally filtering by date range."""

if __name__ == "__main__":
    feud.run([create_post, delete_posts, list_posts])

You can also use a dict to rename the generated commands:

feud.run({"create": create_post, "delete": delete_posts, "list": list_posts})

For more complex applications, you can also nest commands in sub-groups:

feud.run({"list": list_posts, "modify": [create_post, delete_posts]})

If commands are defined in another module, you can also run the module directly and Feud will pick up all runnable objects:

import post

feud.run(post)

You can even call feud.run() without providing any object, and it will automatically discover all runnable objects in the current module.

As you can see, building a CLI using Feud does not require learning many new magic methods or a domain-specific language – you can just use the simple Python you know and ❤️!

Registering command sub-groups

Groups can be registered as sub-groups under other groups. This is a common pattern in CLIs, allowing for interfaces packed with lots of functionality, but still organized in a sensible way.

<table> <tr> <td>

Example: CLI with the following structure for running and managing a blog.

  • blog: Group to manage and serve a blog.
    • serve: Command to run the blog HTTP server.
    • post: Sub-group to manage blog posts.
      • create: Command to create a blog post.
      • delete: Command to delete blog posts.
      • list: Command to view all blog posts.
</td> </tr> <tr> <td>
# blog.py

import feud
from datetime import date
from typing import Literal

class Blog(feud.Group):
    """Manage and serve a blog."""

    def serve(port: int, *, watch: bool = True, env: Literal["dev", "prod"] = "dev"):
        """Start a local HTTP server."""

class Post(feud.Gr
View on GitHub
GitHub Stars63
CategoryDevelopment
Updated2mo ago
Forks2

Languages

Python

Security Score

100/100

Audited on Jan 29, 2026

No findings