Easily make your app extensible by you or others via use of setuptools entrypoints.
Project description
Safdie
Easily make your app extensible by you or others via use of setuptools entrypoints.
- Free software: MIT license
I'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.
Installation
pip install safdie
You can also install the in-development version with:
pip install https://github.com/coddingtonbear/safdie/archive/master.zip
Quickstart
The below example isn't particularly useful, but does demonstrate a fully-working use of this.
- Create your commands as subclasses of
safdie.BaseCommand
and write whatever command classes you need:
# Module Path: my_app.commands
from safdie import BaseCommand
class MyCommand(BaseCommand):
def handle(self):
print("Do whatever you need to do here")
- Create your program's main command-line function:
# Module Path: my_app.cli
from safdie import SafdieRunner, BaseCommand
def main():
# 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 with
python setup.py install
Now you can run my_command_line_app somecommand
to execute your function.
Tips
Adding arguments
Maybe you want to add a command-line flag to your app; you can add those by subclassing SafdieRunner
and defining an override for add_arguments
as shown below:
from argparse import ArgumentParser
from safdie import SafdieRunner
class MyRunner(SafdieRunner):
def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument("--something", action="store_true")
def main():
MyRunner("myapp.commands").run()
Customizing your argument parser
By default, Safdie will generate a new argument parser for you, but maybe you want to use Gooey
or some other Argparse-compatible parser? You can provide the class to use for generating the argument parser by specifying the parser_class
command-line argument:
from gooey import GooeyParser, Gooey
@Gooey
def main():
MyRunner("myapp.commands", parser_class=GooeyParser).run()
Doing something before executing a command
Maybe you want to be able to optionally start a debugger between parsing args and executing the command?
import argparse
from safdie import SafdieRunner
from typing import Any, Dict, Iterable
class MyRunner(SafdieRunner):
def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument("--debugger", action="store_true')
def handle(
self,
args: argparse.Namespace,
init_args: Iterable[Any],
init_kwargs: Dict[str, Any],
handle_args: Iterable[Any],
handle_kwargs: Dict[str, Any],
) -> Any:
if args.debugger:
import debugpy
debugpy.listen(("0.0.0.0", 5678))
debugpy.wait_for_client()
super().handle(
args,
init_args,
init_kwargs,
handle_args,
handle_kwargs
)
def main():
SafdieRunner("myapp.commands").run()
Using your own command subclass
In 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 to handle
. See the source for more details.
# Module Path: my_app.commands
from safdie import BaseCommand
class MyAppCommandBase(BaseCommand):
def __init__(self, some_additional_init_param, *args, **kwargs):
# Do something with `some_additional_init_param
super().__init__(*args, **kwargs)
class MyCommand(MyAppBaseCommand):
def handle(self):
print("Do whatever you need to do here")
from typing import Any, Dict, Iterable
from safdie import SafdieRunner
from .commands import MyAppCommandBase
class MyRunner(SafdieRunner):
def handle(
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
)
def main():
runner = 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 named Habitat 67. Moshe Safdie is the man who designed it.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file safdie-2.0.0.tar.gz
.
File metadata
- Download URL: safdie-2.0.0.tar.gz
- Upload date:
- Size: 12.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.2 importlib_metadata/4.6.4 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.1 CPython/3.9.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5310696c32c1dc513d2545d9963011f4969c84dfb67e357d32480300e5c45961 |
|
MD5 | 98008d1d79d41ca7f75ffa979f8e06d0 |
|
BLAKE2b-256 | 759a9f199c5d16b86d5d8bcf00132482c0a6a9dad2febd555e94c1a5e4685145 |
File details
Details for the file safdie-2.0.0-py2.py3-none-any.whl
.
File metadata
- Download URL: safdie-2.0.0-py2.py3-none-any.whl
- Upload date:
- Size: 7.5 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.2 importlib_metadata/4.6.4 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.1 CPython/3.9.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | c34aa66bba3ca91ccfd116e7ff82bab1c6ead9514b1feab8f1172662b535f680 |
|
MD5 | e409746ab804f086c36f470bb9cba951 |
|
BLAKE2b-256 | 27b68e5b7e92b2ba5e5cc4fa9109ccf4f8f714b95978c474642e5026f3e03b55 |