Package: objclick
objclickobjclickprovides a drop-in replacement forclick, extending it to work in
various OOP contexts.clickis a very nice package for quickly and easily defining composable
command-line interfaces in a declarative manner by stacking decorators on
top of functions that implement the "main" functions of a commands.However, by design, it does not play well in OOP contexts.  In particular,
it is not so easy to promote an instance method of a class to aclickcommand.  This package attempts to rectify that by providing wrappers aroundclickthat also play well with classes in some cases.To give a motivating example, say you have a base class that implements a
CLI:>>>importobjclickasclick>>>importabc>>>classBaseService(metaclass=abc.ABCMeta):...defexplain(self):..."""Explain this service; must be implemented by subclasses."""......@click.command()...defmain(self):...print('Hello, let me tell you about myself.')...self.explain()...This class must now be subclassed with an implementation ofexplain, but
the subclass need not re-implement the rest of the CLI:>>>classMyService(BaseService):...defexplain(self):...print(f'I am an instance of{self.__class__.__name__}.')...SinceMyService.mainis an instance method, we cannot simply callMyService.main()to run the "main" function of CLI.  Just like with a
normal instance method of a class, we must instantiate the class first and
call the method on the instance:>>>service=MyService()>>>service.main([],standalone_mode=False)Hello,letmetellyouaboutmyself.IamaninstanceofMyService.(note:standalone_modeis a standard argument toclickmain functions that is useful for testing
commands.)The inititial version of this package is still experimental, but it
implements a number of other useful cases.One such case is given by theclassgroupdecorator.  This allows defining
a command group on a classmethod-like method that is bound to the class
rather than an instance of the class.  In the common case where a
classmethod implements an alternative constructor for a class, if the
classgroup returns aninstanceof the class it's define on, this instance
will be passed as theselfargument to any instance methods that are added
as subcommands of the group.For example, here is command group that takes a--configoption, as a
configuration is needed to instantiate theServiceclass.  All subcommands
ofService.maincan then access the configuration:>>>importobjclickasclick>>>importjson,pprint>>>classService:...def__init__(self,config):..."""Instantiate `Service` with a configuration dict."""......self.config=config......@click.classgroup()...@click.option('--config',type=click.File())...defmain(cls,config=None):...ifconfigisnotNone:...withconfigasf:...config=json.load(f)...else:...config={}......print(f'Starting up{cls.__name__}...')...returncls(config)......@main.command()...defshow_config(self):...print('Config:',end=' ')...pprint.pprint(self.config)...Now the CLI defined byServicecan be invoked like:>>>importtempfile>>>config={'option1':'a','option2':'b'}>>>withtempfile.NamedTemporaryFile(mode='w')asf:...json.dump(config,f)...f.flush()...# like `service.py --config <config-file> show-config`...args=['--config',f.name,'show-config']...Service.main(args,standalone_mode=False)...StartingupService...Config:{'option1':'a','option2':'b'}objclick Changelogv0.1.1 (2020-10-02)Bug fixesFixed a bug with calling the callback of aclasscommandor aclassgroupviasuper()in a subclass.v0.1.0 (2020-10-01)Initial release.