Alias for a chain of commands

2024/10/8 6:27:35

I have a tool with commands: step1, step2 and step3.

I can chain them by calling:

$ tool step1 step2 step3

I would like to have an alias named all to run all the steps by calling:

$ tool all

I have found a solution that works but it doesn't seem right for me because of calling cli() twice under the hood:

@click.group(chain=True)
def cli():print('cli() has been called')...@cli.command()
def all():cli(args=['step1', 'step2', 'step3'])

How else could this be done without the side effect of calling cli() twice?

Answer

One way to provide some aliases is to intercept the command and directly manipulate the args list. That can be done with a custom class like:

Custom Class

This class overrides the click.Group.__call__() method to allow editing the args list before calling the command processor. In addition it overrides format_epilog to add help documentation for the aliases.

class ExpandAliasesGroup(click.Group):def __init__(self, *args, **kwargs):self.aliases = kwargs.pop('aliases', {})super(ExpandAliasesGroup, self).__init__(*args, **kwargs)def __call__(self, *args, **kwargs):if args and args[0] and args[0][0] in self.aliases:alias = self.aliases[args[0][0]]args[0].pop(0)for command in reversed(alias):args[0].insert(0, command)return super(ExpandAliasesGroup, self).__call__(*args, **kwargs)@propertydef alias_help(self):return '\n'.join('{}:  {}'.format(alias, ' '.join(commands))for alias, commands in sorted(self.aliases.items()))def format_epilog(self, ctx, formatter):"""Inject our aliases into the help string"""if self.aliases:formatter.write_paragraph()formatter.write_text('Aliases:')with formatter.indentation():formatter.write_text(self.alias_help)# call the original epilogsuper(ExpandAliasesGroup, self).format_epilog(ctx, formatter)

Using the Custom Class

By passing the cls parameter, and a dict of aliases to the click.group() decorator, the ExpandAliasesGroup class can do alias expansion.

aliases = dict(all='command1 command2 command3'.split())@click.group(chain=True, cls=ExpandAliasesGroup, aliases=aliases)
def cli():....

How does this work?

This works because click is a well designed OO framework. The @click.group() decorator usually instantiates a click.Group object but allows this behavior to be over ridden with the cls parameter. So it is a relatively easy matter to inherit from click.Group in our own class and over ride the desired methods.

By overriding the __call__ method we can intercept all command calls. Then if the list of args starts with a known alias, we edit the args list by removing that aliased command and replacing it with the aliases.

By overriding the format_epilog method we can add help documentation for the aliases.

Test Code:

import clickaliases = dict(all='command1 command2 command3'.split())@click.group(cls=ExpandAliasesGroup, chain=True, aliases=aliases)
def cli():pass@cli.command()
def command1():click.echo('Command 1')@cli.command()
def command2():click.echo('Command 2')@cli.command()
def command3():click.echo('Command 3')if __name__ == "__main__":commands = ('command1','command3','command1 command2','all','--help',)for cmd in commands:try:print('-----------')print('> ' + cmd)cli(cmd.split())except:pass        

Test Results:

-----------
> command1
Command 1
-----------
> command3
Command 3
-----------
> command1 command2
Command 1
Command 2
-----------
> all
Command 1
Command 2
Command 3
-----------
> --help
Usage: test.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...Options:--help  Show this message and exit.Commands:command1  Command #1 comes firstcommand2  Command #2 is after command #1command3  Command #3 saves the best for lastAliases:all:  command1 command2 command3
https://en.xdnf.cn/q/70147.html

Related Q&A

Generate misspelled words (typos)

I have implemented a fuzzy matching algorithm and I would like to evaluate its recall using some sample queries with test data. Lets say I have a document containing the text:{"text": "T…

Get the inverse function of a polyfit in numpy

I have fit a second order polynomial to a number of x/y points in the following way:poly = np.polyfit(x, y, 2)How can I invert this function in python, to get the two x-values corresponding to a speci…

Installing an old version of scikit-learn

Problem StatmentIm trying to run some old python code that requires scikit-learn 18.0 but the current version I have installed is 0.22 and so Im getting a warning/invalid data when I run the code.What …

remove characters from pandas column

Im trying to simply remove the ( and ) from the beginning and end of the pandas column series. This is my best guess so far but it just returns empty strings with () intact. postings[location].replace(…

numerically stable inverse of a 2x2 matrix

In a numerical solver I am working on in C, I need to invert a 2x2 matrix and it then gets multiplied on the right side by another matrix:C = B . inv(A)I have been using the following definition of an …

Type annotating class variable: in init or body?

Lets consider the two following syntax variations:class Foo:x: intdef __init__(self, an_int: int):self.x = an_intAndclass Foo:def __init__(self, an_int: int):self.x = an_intApparently the following cod…

decoding shift-jis: illegal multibyte sequence

Im trying to decode a shift-jis encoded string, like this:string.decode(shift-jis).encode(utf-8)to be able to view it in my program.When I come across 2 shift-jis characters, in hex "0x87 0x54&quo…

Add columns in pandas dataframe dynamically

I have following code to load dataframe import pandas as pdufo = pd.read_csv(csv_path) print(ufo.loc[[0,1,2] , :])which gives following output, see the structure of the csvCity Colors Reported Shape Re…

How do you add input from user into list in Python [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.Want to improve this question? Add details and clarify the problem by editing this post.Closed 9 years ago.Improve…

How to suppress matplotlib inline for a single cell in Jupyter Notebooks/Lab?

I was looking at matplotlib python inline on/off and this kind of solves the problem but when I do plt.ion() all of the Figures pop up (100s of figures). I want to keep them suppressed in a single cel…