Package: safdie
SafdieEasily make your app extensible by you or others via use of setuptools entrypoints.Free software: MIT licenseI've written roughly the same module system for ten or so command-line apps over the last few years, and by now I've landed on a pattern that I've found pretty flexible and useful.  Here, I've packed it into a module so both you and I can avoid re-inventing it every time we have a new project.Installationpip install safdieYou can also install the in-development version with:pip install https://github.com/coddingtonbear/safdie/archive/master.zipQuickstartThe below example isn't particularly useful, but does demonstrate a fully-working use of this.Create your commands as subclasses ofsafdie.BaseCommandand write whatever command classes you need:# Module Path: my_app.commandsfromsafdieimportBaseCommandclassMyCommand(BaseCommand):defhandle(self):print("Do whatever you need to do here")Create your program's main command-line function:# Module Path: my_app.clifromsafdieimportSafdieRunner,BaseCommanddefmain():# This will look up the command and run it's `handle` function.SafdieRunner("myapp.commands").run()In setuptools entrypoints, declare your entrypoints for both your command-line entrypoint and each of your commands:setup(...entrypoints={"console_scripts":["my_command_line_app = my_app.cli:main",],"myapp.commands":{"somecommand = my_app.commands:MyCommand",}})Install your app withpython setup.py installNow you can runmy_command_line_app somecommandto execute your function.TipsAdding argumentsMaybe you want to add a command-line flag to your app; you can add those by subclassingSafdieRunnerand defining an override foradd_argumentsas shown below:fromargparseimportArgumentParserfromsafdieimportSafdieRunnerclassMyRunner(SafdieRunner):defadd_arguments(self,parser:ArgumentParser)->None:parser.add_argument("--something",action="store_true")defmain():MyRunner("myapp.commands").run()Customizing your argument parserBy default, Safdie will generate a new argument parser for you, but maybe you want to useGooeyor some other Argparse-compatible parser?  You can provide the class to use for generating the argument parser by specifying theparser_classcommand-line argument:fromgooeyimportGooeyParser,GooeyfromsafdieimportSafdieRunner@Gooeydefmain():SafdieRunner("myapp.commands",parser_class=GooeyParser).run()Doing something before executing a commandMaybe you want to be able to optionally start a debugger between parsing args and executing the command?importargparsefromsafdieimportSafdieRunnerfromtypingimportAny,Dict,IterableclassMyRunner(SafdieRunner):defadd_arguments(self,parser:ArgumentParser)->None:parser.add_argument("--debugger",action="store_true')defhandle(self,args:argparse.Namespace,init_args:Iterable[Any],init_kwargs:Dict[str,Any],handle_args:Iterable[Any],handle_kwargs:Dict[str,Any],)->Any:ifargs.debugger:importdebugpydebugpy.listen(("0.0.0.0",5678))debugpy.wait_for_client()super().handle(args,init_args,init_kwargs,handle_args,handle_kwargs)defmain():MyRunner("myapp.commands").run()Using your own command subclassIn the below example, you have your own command subclass that requires an additional parameter at init-time.  Although the example below only uses an extra parameter for__init__, you can also pass extra parameters tohandle.  See the source for more details.# Module Path: my_app.commandsfromsafdieimportBaseCommandclassMyAppCommandBase(BaseCommand):def__init__(self,some_additional_init_param,*args,**kwargs):# Do something with `some_additional_init_paramsuper().__init__(*args,**kwargs)classMyCommand(MyAppBaseCommand):defhandle(self):print("Do whatever you need to do here")fromtypingimportAny,Dict,IterablefromsafdieimportSafdieRunnerfrom.commandsimportMyAppCommandBaseclassMyRunner(SafdieRunner):defhandle(self,args:argparse.Namespace,init_args:Iterable[Any],init_kwargs:Dict[str,Any],handle_args:Iterable[Any],handle_kwargs:Dict[str,Any],)->Any:some_value_i_want_to_pass="Arbitrary"init_kwargs['some_additional_init_param']=(some_value_i_want_to_pass)super().handle(args,init_args,init_kwargs,handle_args,handle_kwargs)defmain():MyRunner("myapp.commands",cmd_class=MyAppCommandBase).run()Why is this named 'Safdie'?You've probably seen at least a few photos of the famous building namedHabitat 67.Moshe Safdieis the man who designed it.