This question is about the Python Click library.
I want click to gather my commandline arguments. When gathered, I want to reuse these values. I dont want any crazy chaining of callbacks, just use the return value. By default, click disables using the return value and calls sys.exit().
I was wondering how to correctly invoke standalone_mode (http://click.pocoo.org/5/exceptions/#what-if-i-don-t-want-that) in case I want to use the decorator style. The above linked doc only shows the usage when (manually) creating Commands using click.
Is it even possible? A minimal example is shown below. It illustrates how click calls sys.exit() directly after returning from gatherarguments
import click
#click.command()
#click.option('--name', help='Enter Name')
#click.pass_context
def gatherarguments(ctx, name):
return ctx
def usectx(ctx):
print("Name is %s" % ctx.params.name)
if __name__ == '__main__':
ctx = gatherarguments()
print(ctx) # is never called
usectx(ctx) # is never called
$ python test.py --name Your_Name
I would love this to be stateless, meaning, without any click.group functionality - I just want the results, without my application exiting.
Just sending standalone_mode as a keyword argument worked for me:
from __future__ import print_function
import click
#click.command()
#click.option('--name', help='Enter Name')
#click.pass_context
def gatherarguments(ctx, name):
return ctx
def usectx(ctx):
print("Name is %s" % ctx.params['name'])
if __name__ == '__main__':
ctx = gatherarguments(standalone_mode=False)
print(ctx)
usectx(ctx)
Output:
./clickme.py --name something
<click.core.Context object at 0x7fb671a51690>
Name is something
Related
I have a python script using the "click" library to run various actions. In the below code, I attempted to use a pre-existing function in a new function. I tried to follow the click documentation to use the Context invoke and forward (which seems to work). But I get the error on execution of the make_connection function: "use_connection() takes 0 positional arguments but 1 was given". I am unsure what I'm doing wrong here. Please help.
from click.core import Context
import pysftp
import click
...
#click.group()
def cli():
pass
#cli.command('make_connection')
def make_connection():
return pysftp.Connection(server, username=username, password=sftp_key)
#cli.command('use_connection')
#click.pass_context
def use_connection():
Context.forward(make_connection)
connection = Context.invoke(make_connection)
if __name__ == '__main__':
cli()
You need to define cli() like this:
def cli(ctx):
...
A context obj is passed to cli as a first argument.
I'm a new bee for python currently working on the Click module. So here I have a doubt to providing input for the main cli function only. But I want to provide the input for my all the function one by one. is it possible to click? Thanks for advance.
#click.option('--create', default='sub', help='Create')
#click.command()
def create(create):
click.echo('create called')
os.system('curl http://127.0.0.1:5000/create')
#click.option('--conn', default='in', help='connect to server')
#click.command()
def conn(conn):
click.echo('conn called')
os.system('curl http://127.0.0.1:5000/')
and my setup.py
from setuptools import setup
setup(
name="hello",
version='0.1',
py_modules=['hello'],
install_requires=[
'Click',
],
entry_points='''
[console_scripts]
hello=hello:cli
''',
)
My output expectation
$ hello --conn in
success
hello --create sub
success
Sounds to me like you want different commands based on input provided to your hello cli. For that reason, Click has the useful notion of a group, a collection of commands that can be invoked.
You can reorganize your code as follows:
#click.group()
def cli():
pass
#cli.command()
def create():
click.echo('create called')
os.system('curl http://127.0.0.1:5000/create')
#cli.command()
def conn():
click.echo('conn called')
os.system('curl http://127.0.0.1:5000/')
def main():
value = click.prompt('Select a command to run', type=click.Choice(list(cli.commands.keys()) + ['exit']))
while value != 'exit':
cli.commands[value]()
if __name__ == "__main__":
main()
and the calls would be:
$ hello con
$ hello create
It doesn't seem like you need the options, as you don't change the behaviour of each command based on the option being passed in or not. For more information, please refer to the commands and groups Click documentation
I'm trying to add help to the command line application using click library. As mentioned in official documentation,
For commands, a short help snippet is generated. By default, it’s the
first sentence of the help message of the command, unless it’s too
long. This can also be overridden
With simple #click.command everything works as expected:
import click
#click.command()
def cli():
"""This is sample description of script."""
if __name__ == '__main__':
cli()
Running this would display description for the script from the method's doscstring:
Usage: example.py [OPTIONS]
This is sample description of script.
Options:
--help Show this message and exit.
But I need to use CommandCollection, as I'm creating a script consisting from multiple commands. Here is an example from official help:
import click
#click.group()
def cli1():
pass
#cli1.command()
def cmd1():
"""Command on cli1"""
#click.group()
def cli2():
pass
#cli2.command()
def cmd2():
"""Command on cli2"""
cli = click.CommandCollection(sources=[cli1, cli2])
if __name__ == '__main__':
cli()
And I don't know how to add description to whole command collection. What I've tried so far:
provide help with additional short_help parameter
set __doc__ argument for cli parameter, after creating CommandCollection
add docstring to cli1 method, decorated with #click.group
Any help is much appreciated.
Just use help parameter:
cli = click.CommandCollection(sources=[cli1, cli2], help="This would be your description, dude!")
I use a simple command to run my tests with nose:
#manager.command
def test():
"""Run unit tests."""
import nose
nose.main(argv=[''])
However, nose supports many useful arguments that now I could not pass.
Is there a way to run nose with manager command (similar to the call above) and still be able to pass arguments to nose? For example:
python manage.py test --nose-arg1 --nose-arg2
Right now I'd get an error from Manager that --nose-arg1 --nose-arg2 are not recognised as valid arguments. I want to pass those args as nose.main(argv= <<< args comming after python manage.py test ... >>>)
Flask-Script has an undocumented capture_all_flags flag, which will pass remaining args to the Command.run method. This is demonstrated in the tests.
#manager.add_command
class NoseCommand(Command):
name = 'test'
capture_all_args = True
def run(self, remaining):
nose.main(argv=remaining)
python manage.py test --nose-arg1 --nose-arg2
# will call nose.main(argv=['--nose-arg1', '--nose-arg2'])
In the sources of flask_script you can see that the "too many arguments" error is prevented when the executed Command has the attribute capture_all_args set to True which isn't documented anywhere.
You can set that attribute on the class just before you run the manager
if __name__ == "__main__":
from flask.ext.script import Command
Command.capture_all_args = True
manager.run()
Like this additional arguments to the manager are always accepted.
The downside of this quick fix is that you cannot register options or arguments to the commands the normal way anymore.
If you still need that feature you could subclass the Manager and override the command decorator like this
class MyManager(Manager):
def command(self, capture_all=False):
def decorator(func):
command = Command(func)
command.capture_all_args = capture_all
self.add_command(func.__name__, command)
return func
return decorator
Then you can use the command decorator like this
#manager.command(True) # capture all arguments
def use_all(*args):
print("args:", args[0])
#manager.command() # normal way of registering arguments
def normal(name):
print("name", name)
Note that for some reason flask_script requires use_all to accept a varargs but will store the list of arguments in args[0] which is a bit strange. def use_all(args): does not work and fails with TypeError "got multiple values for argument 'args'"
Ran into an issue with davidism's soln where only some of the args were being received
Looking through the docs a bit more it is documented that nose.main automatically picks up the stdin
http://nose.readthedocs.io/en/latest/api/core.html
So we are now just using:
#manager.add_command
class NoseCommand(Command):
name = 'nose'
capture_all_args = True
def run(self, remaining):
nose.main()
I'm using click (http://click.pocoo.org/3/) to create a command line application, but I don't know how to create a shell for this application.
Suppose I'm writing a program called test and I have commands called subtest1 and subtest2
I was able to make it work from terminal like:
$ test subtest1
$ test subtest2
But what I was thinking about is a shell, so I could do:
$ test
>> subtest1
>> subtest2
Is this possible with click?
This is not impossible with click, but there's no built-in support for that either. The first you would have to do is making your group callback invokable without a subcommand by passing invoke_without_command=True into the group decorator (as described here). Then your group callback would have to implement a REPL. Python has the cmd framework for doing this in the standard library. Making the click subcommands available there involves overriding cmd.Cmd.default, like in the code snippet below. Getting all the details right, like help, should be doable in a few lines.
import click
import cmd
class REPL(cmd.Cmd):
def __init__(self, ctx):
cmd.Cmd.__init__(self)
self.ctx = ctx
def default(self, line):
subcommand = cli.commands.get(line)
if subcommand:
self.ctx.invoke(subcommand)
else:
return cmd.Cmd.default(self, line)
#click.group(invoke_without_command=True)
#click.pass_context
def cli(ctx):
if ctx.invoked_subcommand is None:
repl = REPL(ctx)
repl.cmdloop()
#cli.command()
def a():
"""The `a` command prints an 'a'."""
print "a"
#cli.command()
def b():
"""The `b` command prints a 'b'."""
print "b"
if __name__ == "__main__":
cli()
There is now a library called click_repl that does most of the work for you. Thought I'd share my efforts in getting this to work.
The one difficulty is that you have to make a specific command the repl command, but we can repurpose #fpbhb's approach to allow calling that command by default if another one isn't provided.
This is a fully working example that supports all click options, with command history, as well as being able to call commands directly without entering the REPL:
import click
import click_repl
import os
from prompt_toolkit.history import FileHistory
#click.group(invoke_without_command=True)
#click.pass_context
def cli(ctx):
"""Pleasantries CLI"""
if ctx.invoked_subcommand is None:
ctx.invoke(repl)
#cli.command()
#click.option('--name', default='world')
def hello(name):
"""Say hello"""
click.echo('Hello, {}!'.format(name))
#cli.command()
#click.option('--name', default='moon')
def goodnight(name):
"""Say goodnight"""
click.echo('Goodnight, {}.'.format(name))
#cli.command()
def repl():
"""Start an interactive session"""
prompt_kwargs = {
'history': FileHistory(os.path.expanduser('~/.repl_history'))
}
click_repl.repl(click.get_current_context(), prompt_kwargs=prompt_kwargs)
if __name__ == '__main__':
cli(obj={})
Here's what it looks like to use the REPL:
$ python pleasantries.py
> hello
Hello, world!
> goodnight --name fpbhb
Goodnight, fpbhb.
And to use the command line subcommands directly:
$ python pleasntries.py goodnight
Goodnight, moon.
I know this is super old, but I've been working on fpbhb's solution to support options as well. I'm sure this could use some more work, but here is a basic example of how it could be done:
import click
import cmd
import sys
from click import BaseCommand, UsageError
class REPL(cmd.Cmd):
def __init__(self, ctx):
cmd.Cmd.__init__(self)
self.ctx = ctx
def default(self, line):
subcommand = line.split()[0]
args = line.split()[1:]
subcommand = cli.commands.get(subcommand)
if subcommand:
try:
subcommand.parse_args(self.ctx, args)
self.ctx.forward(subcommand)
except UsageError as e:
print(e.format_message())
else:
return cmd.Cmd.default(self, line)
#click.group(invoke_without_command=True)
#click.pass_context
def cli(ctx):
if ctx.invoked_subcommand is None:
repl = REPL(ctx)
repl.cmdloop()
#cli.command()
#click.option('--foo', required=True)
def a(foo):
print("a")
print(foo)
return 'banana'
#cli.command()
#click.option('--foo', required=True)
def b(foo):
print("b")
print(foo)
if __name__ == "__main__":
cli()
I was trying to do something similar to the OP, but with additional options / nested sub-sub-commands. The first answer using the builtin cmd module did not work in my case; maybe with some more fiddling.. But I did just run across click-shell. Haven't had a chance to test it extensively, but so far, it seems to work exactly as expected.