Skip to content

Commit

Permalink
feat: add feud.rename decorator (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
eonu authored Dec 14, 2023
1 parent b1f0dcf commit a364b56
Show file tree
Hide file tree
Showing 19 changed files with 517 additions and 94 deletions.
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ 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.
**In red is a typical Click implementation, and in green is the Feud equivalent.**

```diff
- import click
Expand Down Expand Up @@ -472,8 +472,8 @@ $ python blog.py post list --help

### Powerful typing

Feud is powered by Pydantic – a validation library with extensive support for
many data types, including:
Feud is powered by [Pydantic](https://docs.pydantic.dev/latest/) – a
validation library with extensive support for many data types, including:

- simple types such as integers and dates,
- complex types such as emails, IP addresses, file/directory paths, database
Expand Down Expand Up @@ -742,22 +742,26 @@ You can install Feud using `pip`.
The latest stable version of Feud can be installed with the following command.

```console
pip install feud[all]
pip install "feud[all]"
```

This installs Feud with the optional dependencies:

- [`rich-click`](https://github.com/ewels/rich-click) (can install individually with `pip install feud[rich]`)<br/>
- [`rich-click`](https://github.com/ewels/rich-click) (can install individually with `pip install "feud[rich]"`)<br/>
_Provides improved formatting for CLIs produced by Feud._
- [`pydantic-extra-types`](https://github.com/pydantic/pydantic-extra-types) (can install individually with `pip install feud[extra-types]`)<br/>
- [`pydantic-extra-types`](https://github.com/pydantic/pydantic-extra-types) (can install individually with `pip install "feud[extra-types]"`)<br/>
_Provides additional types that can be used as type hints for Feud commands._
- [`email-validator`](https://github.com/JoshData/python-email-validator) (can install individually with `pip install feud[email]`)<br/>
- [`email-validator`](https://github.com/JoshData/python-email-validator) (can install individually with `pip install "feud[email]"`)<br/>
_Provides Pydantic support for email validation._

To install Feud without any optional dependencies, simply run `pip install feud`.

> [!CAUTION]
> Feud **will break** if used with postponed type hint evaluation ([PEP563](https://peps.python.org/pep-0563/)), i.e. `from __future__ import annotations`.
> Feud **will break** if used with postponed type hint evaluation ([PEP563](https://peps.python.org/pep-0563/)), i.e.:
>
> ```python
> from __future__ import annotations
> ```
>
> This is because Feud relies on type hint evaluation in order to determine the expected input type for command parameters.
Expand Down Expand Up @@ -890,7 +894,7 @@ All contributions to this repository are greatly appreciated. Contribution guide
> <img src="https://i.postimg.cc/jq3MZSTD/avatar.png" align="left"/>
> <b>We're living in an imperfect world!</b><br/>
> <sup>Feud is in a public beta-test phase, likely with <em>lots</em> of bugs. Please leave feedback if you come across anything strange!</sup>
> <sup>Feud is in a public beta-test phase, likely with <em>lots</em> of bugs. Please <a href="https://github.com/eonu/feud/issues/new">leave feedback</a> if you come across anything strange!</sup>
## Licensing
Expand Down
12 changes: 6 additions & 6 deletions docs/source/sections/config/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
Configuration
=============

.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

:doc:`../core/command` are defined by :py:func:`.command`,
which accepts various Feud configuration key-word arguments such as
``negate_flags`` or ``show_help_defaults`` directly.
Expand All @@ -15,12 +21,6 @@ object that can be provided to other commands or groups. This functionality is
implemented by :py:func:`.config`, which creates a configuration which can be
provided to :py:func:`.command` or :py:class:`.Group`.

.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

----

API reference
Expand Down
12 changes: 6 additions & 6 deletions docs/source/sections/core/command.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Commands
========

.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

Commands are the core component of a CLI, running a user-defined function that
may be parameterized with arguments or options.

Expand All @@ -18,12 +24,6 @@ Commands may be executed using :py:func:`.run`.
- `Arguments <https://click.palletsprojects.com/en/8.1.x/arguments/>`__
- `Options <https://click.palletsprojects.com/en/8.1.x/options/>`__

.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

----

Understanding function signatures
Expand Down
12 changes: 6 additions & 6 deletions docs/source/sections/core/group.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Groups
======

.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

Groups are a component of CLIs that allow you to group together related :doc:`command`.

In addition to commands, groups may also contain further nested groups by :py:obj:`.register`\ ing subgroups,
Expand All @@ -18,12 +24,6 @@ Groups and their subgroups or commands can be executed using :py:func:`.run`.
- `Arguments <https://click.palletsprojects.com/en/8.1.x/arguments/>`__
- `Options <https://click.palletsprojects.com/en/8.1.x/options/>`__

.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

----

API reference
Expand Down
12 changes: 6 additions & 6 deletions docs/source/sections/decorators/alias.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Aliasing parameters
===================

.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

In CLIs, it is common for options to have an alias allowing
for quicker short-hand usage.

Expand Down Expand Up @@ -43,12 +49,6 @@ and can instead rely on type hints and docstrings.

In the case of boolean flags such as ``--verbose`` in this case, the ``--no-verbose``
option will also have a corresponding ``--no-v`` alias automatically defined.

.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

----

Expand Down
12 changes: 6 additions & 6 deletions docs/source/sections/decorators/env.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Using environment variables
===========================

.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

In CLIs, environment variables are often used as an alternative method of
providing input for options. This is particularly useful for sensitive
information such as API keys, tokens and passwords.
Expand Down Expand Up @@ -52,12 +58,6 @@ and can instead rely on type hints and docstrings.
feud.run(my_command)
This can be called with ``SECRET_TOKEN=hello-world python command.py``, for example.

.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

----

Expand Down
2 changes: 1 addition & 1 deletion docs/source/sections/decorators/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ This module consists of decorators that modify :doc:`../core/command` and their

alias.rst
env.rst
.. rename.rst
rename.rst
159 changes: 155 additions & 4 deletions docs/source/sections/decorators/rename.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,162 @@
Renaming parameters
===================
Renaming commands/parameters
============================

TODO
.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

In certain cases, it may be desirable or even necessary for the names of the
commands or parameters generated by Feud to be different to the names of the
Python functions (and their parameters) that were used to generate the
commands.

The :py:func:`.rename` operator can be used in these scenarios to rename commands
or parameters.

Examples
--------

Defining commands or parameters with reserved keywords
******************************************************

Suppose we have the following command, ``sum``,
which takes a starting number ``from``, and an ending number ``to``,
and sums all numbers between and including the starting and ending number.

This might be called in the following way:

.. code:: bash
$ sum --from 1 --to 10
With generating code that might look like:

.. code:: python
# sum.py
import feud
def sum(*, from: int, to: int):
"""Sums the numbers between and including a start and end number.
Parameters
----------
from:
Starting number.
to:
Ending number.
"""
print(sum(range(from, to + 1)))
if __name__ == "__main__":
feud.run(sum)
There are two problems here:

1. By naming the function ``sum``, we are shadowing the in-built Python
function ``sum``. This is also an issue as our function actually relies
on the in-built Python ``sum`` function to actually do the addition.
2. ``from`` is also a reserved Python keyword which is used in module imports,
and cannot be used as a function parameter.

We can use the :py:func:`.rename` decorator to rename both the command and parameter.

.. code:: python
# sum.py
import feud
@feud.rename("sum", from_="from")
def sum_(*, from_: int, to: int):
"""Sums the numbers between and including a start and end number.
Parameters
----------
from_:
Starting number.
to:
Ending number.
"""
print(sum(range(from, to + 1)))
if __name__ == "__main__":
feud.run(sum_)
This gives us valid Python code, and also our expected CLI behaviour.

Defining hyphenated commands or parameters
******************************************

Suppose we have a command that should be called in the following way:

.. code:: bash
$ say-hi --welcome-message "Hello World!"
As Feud uses the parameter names present in the Python function signature as
the parameter names for the generated CLI, this means that defining parameters
with hyphens is *usually* not possible, as Python identifiers cannot have hyphens.
Similarly, a function name cannot have a hyphen:

.. code:: python
# hyphen.py
import feud
def say-hi(*, welcome-message: str):
print(welcome-message)
if __name__ == "__main__":
feud.run(say-hi)
We can use the :py:func:`.rename` decorator to rename both the command and parameter.

.. code:: python
# hyphen.py
import feud
@feud.rename("say-hi", welcome_message="welcome-message")
def say_hi(*, welcome_message: str):
print(welcome_message)
if __name__ == "__main__":
feud.run(say_hi)
This gives us valid Python code, and also our expected CLI behaviour.

Special use case for maintaining group-level configurations
***********************************************************

Although :py:func:`.command` accepts a ``name`` argument (passed to Click) that can be
used to rename a command, this can sometimes be undesirable in the case of :doc:`../core/group`.

In the following example, although ``show_help_defaults`` has been set to
``False`` at the group level (which would usually mean that all commands
defined within the group will not have their parameter defaults shown in
``--help``), this has been overridden by the ``feud.command`` call which
has ``show_help_defaults=True`` by default.

.. code:: python
class CLI(feud.Group, show_help_defaults=False):
@feud.command(name="my-func")
def my_func(*, opt: int = 1):
pass
Using ``@feud.rename("my-func")`` instead of ``@feud.command(name="my-func")``
would allow for the group-level configuration to be used, while still renaming
the function.

----

API reference
-------------

TODO
.. autofunction:: feud.decorators.rename
12 changes: 6 additions & 6 deletions docs/source/sections/typing/other.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Other types
===========

.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

Feud provides the following additional types for common CLI needs.

.. tip::
Expand All @@ -16,12 +22,6 @@ Feud provides the following additional types for common CLI needs.
t.Counter # feud.typing.custom.Counter
t.concounter # feud.typing.custom.concounter
.. contents:: Table of Contents
:class: this-will-duplicate-information-and-it-is-still-useful-here
:local:
:backlinks: none
:depth: 3

----

Counting types
Expand Down
Loading

0 comments on commit a364b56

Please sign in to comment.