How can you use a Python @click command decorator in a class method?
Asked Answered
M

1

6

My Python script has a class named Command. I want to call my class' method as a CLI command (something like python cli-comand.py net get-public-ip).

I used the Python click library, but when I tried to execute it, it throws the error TypeError: get_public_ip() missing 1 required positional argument: 'self'.

As expected, I am not able to pass self. Is there any way to pass arguments into the class method, or any other approach? click is mostly used with functions.

My Python script is below:

cli-command.py

import click
import urllib.request
from typing import List, AnyStr
from urllib.error import URLError

from ttn_cli.utils.constants import FETCH_PUBLIC_IP_URL


@click.group()
def cli():
    """
    This is the root function of cli module.
    """
    pass


@cli.group()
def net():
    """
    function act as a group which is linked to root cli function.
    """
    pass


class Command:

    def __init__(self):
        pass

    @net.command()  #  <-- click command
    def get_public_ip(self, *args) -> AnyStr:  # I want to use this method as a cli command
        """
        Function used to get public ip of your host.
        args: List of arguments provided
        """
        try:
            ip = urllib.request.urlopen(FETCH_PUBLIC_IP_URL).read().decode(
                "utf-8")
            return ip
        except URLError:
            return "Please check your internet connectivity"


cmd_network = Command()


def main() -> None:
    """
    This is the main function which returns a shell to user as well
    as can run cli commands directly.
    """
    cli()


if __name__ == "__main__":
    main()
Must answered 16/7, 2020 at 9:19 Comment(0)
K
2

This is a late answer to an old question but I hope that somebody might find it useful in future...

You can use two decorators that would help you achieve that:

  • click.pass_context applied to a group that would give it a context on which you can create an instance of your Command class.
  • click.pass_obj applied to the class' method that would pass that instance as the first argument.

In your example it would look like this:

import click

@click.group()
def cli():
    pass

@cli.group()
@click.pass_context
def net(ctx):
    ctx.obj = Command()

class Command:
    @net.command()
    @click.pass_obj
    def get_public_ip(self, *args):
        ...

def main() -> None:
    cli()

if __name__ == "__main__":
    main()

Notes:

  • It assumes that you have some reason for having main function because otherwise you could call cli() directly in the if __name__ == "__main__" block.
  • This was tested in click 8.1.3. I don't know in what version were the decorators introduced.
Korean answered 18/2, 2023 at 17:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.