hey, is it possible to do
class Foo(object):
@click.command()
def cmd(self):
pass
this code would fail because click will validate that a "argument self" is needed, is it possible to let click not check this in a easy way?
What's the point of doing this? Anyway, you can probably use @staticmethod
I am manipulating the sys.argv and use getattr(obj, cmd_name) to call the cmd, obj is used to share some states between commands, it is not a staticmethod.
@woosley You can use hand-made decorator (instead of click.command()). Like this. With it you can decorate any callable, populating its arguments with those extracted from sys.argv. Probably, it's worth considering adding smth like this to click.
I would like this to work. I am trying to integrate click with another library which requires I define the option parser as a method of a class. Using @staticmethod isn't a great option since it leaves me with nowhere to put the resulting state (except, obviously, I could dump it into a global somewhere - yuck).
Closing as I don't think this is an intended use case. If you want to build a cli in a class, you should probably do that in __init__ by calling cli.add_command() so that the methods are bound.
@davidism can you provide a functionnal example for your thoughts about class and click ?
#!/usr/bin/python
# -*- coding: utf-8 -*-
# standard libs
import os
import sys
import traceback
#import exceptions
# dependency libs
import click
class clii(click.Group) :
def __init__(self):
commandes = { "encode": self.encode,
"decode": self.decode}
click.Group.__init__(self, name="toto", commands = commandes, **{ "chain": False})
#self.add_command(self.encode, "encode")
#self.add_command(self.decode, "decode")
@click.option('--name', prompt='Your name',
help='The person to greet.')
def encode(self, name):
"""Simple program that greets NAME for a total of COUNT times."""
click.echo('Hello %s!' % name)
@click.option('--name', prompt='Your name',
help='The person to greet.')
def decode(self, name):
"""Simple program that greets NAME for a total of COUNT times."""
click.echo('Hello %s!' % name)
if __name__ == '__main__':
# class test
clii()
click.pause()
I try below, but that never handle my command
I am trying do to exactly the same. To answer the question of @ThiefMaster "What's the point of doing this?". I am much more confident in using OOP than imperative programming for this kind of development.
Suppose I am writing a systemd service in Python. This Service features:
start, stop, run, restart, status--pid fileNow I would like to write my own service that connect to some server:
class MyService(Service):
@option('-h', '--host')
def host(self, host):
self.host = host
@option('-p', '--port')
def port(self, port):
self.port = port
@command('test-connection')
@option('-q', '--quiet')
def test(self, quiet):
try:
self.connect()
except Exception as e:
return 1
return 0
...
And of course to run the service:
if __name__ == '__main__':
MyService().run()
It is very convenient to extend existing command line applications which is not possible with the current Click implementation.
Well, I have quickly coded something that kind of work as I would like to
#!/usr/bin/python3
import click
class command:
def __init__(self, name=None, cls=click.Command, **attrs):
self.name = name
self.cls = cls
self.attrs = attrs
def __call__(self, method):
def __command__(this):
def wrapper(*args, **kwargs):
return method(this, *args, **kwargs)
return self.cls(self.name, callback=wrapper, **self.attrs)
method.__command__ = __command__
return method
class option:
def __init__(self, *param_decls, **attrs):
self.param_decls = param_decls
self.attrs = attrs
def __call__(self, method):
if not hasattr(method, '__option__'):
method.__options__ = []
method.__options__.append(
click.Option(param_decls=self.param_decls, **self.attrs))
return method
class Cli:
def __new__(cls, *args, **kwargs):
self = super(Cli, cls).__new__(cls, *args, **kwargs)
def cli(*args, **options):
for callback in self.__option_callbacks__:
callback(self, **options)
self._cli = click.Group(callback=cli)
# Wrap commands
for attr_name in dir(cls):
attr = getattr(cls, attr_name)
if hasattr(attr, '__command__'):
self._cli.add_command(attr.__command__(self))
# Wrap instance options
self.__option_callbacks__ = set()
for attr_name in dir(cls):
attr = getattr(cls, attr_name)
if hasattr(attr, '__options__'):
self._cli.params.extend(attr.__options__)
self.__option_callbacks__.add(attr)
return self
def run(self):
"""Run the CLI application."""
self()
def __call__(self):
"""Run the CLI application."""
self._cli()
class HeyDude(Cli):
@option('-n', '--name', default="Dude")
def set_name(self, name):
self._name = name
@command('shout')
def shout(self):
print("HEY %s!" % self._name.upper())
@command('greet')
def bar(self):
print("Hello %s!" % self._name)
HeyDude()()
It is a little late for this conversation, but I ran into a similar problem, and worked it out with a bit of metaprogramming and functional wrapping. My solution only works for Python3.x, but there is nothing there that would not work for Python2.7 with a change in how the Metaclasses is assigned.
Closing as I don't think this is an intended use case. If you want to build a cli in a class, you should probably do that in
__init__by callingcli.add_command()so that the methods are bound.
@davidism Yes, I thought this way too, but if you'll try to do that all you'll receive is just traceback like this one:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "C:\Users\asalynskiy\AppData\Local\Programs\Python\Python37\lib\importlib\__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
File "<frozen importlib._bootstrap>", line 983, in _find_and_load
File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 728, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "C:\Users\asalynskiy\Documents\Python\temp\click-example\clickexample\cli.py", line 54, in <module>
@click.group(cls=MathCli)
File "C:\Users\asalynskiy\Documents\Python\temp\click-example\.venv\lib\site-packages\click\decorators.py", line 130, in decorator
cmd = _make_command(f, name, attrs, cls)
File "C:\Users\asalynskiy\Documents\Python\temp\click-example\.venv\lib\site-packages\click\decorators.py", line 102, in _make_command
**attrs
File "C:\Users\asalynskiy\Documents\Python\temp\click-example\clickexample\cli.py", line 35, in __init__
func = click.argument(arg[0], type=arg[1])(func)
File "C:\Users\asalynskiy\Documents\Python\temp\click-example\.venv\lib\site-packages\click\decorators.py", line 168, in decorator
_param_memo(f, ArgumentClass(param_decls, **attrs))
File "C:\Users\asalynskiy\Documents\Python\temp\click-example\.venv\lib\site-packages\click\decorators.py", line 151, in _param_memo
f.__click_params__ = []
AttributeError: 'method' object has no attribute '__click_params__'
It's not quite obvious functionality, but for bunch of usecases it would be quite useful to be able "just do it in __init__" instead of searching for some workarounds or reinventing the wheel. 馃槈
@davidism Agree with others here. Python allows for functional and imperative programming, but OOP is predominant. I'm now at my third company where this very issue is being brought up. I really think you should document your suggested approach with a thorough working example.