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.BaseCommandand 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
from safdie import SafdieRunner
@Gooey
def main():
SafdieRunner("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():
MyRunner("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():
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.
Metadata
Release files for safdie 2.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| safdie-2.0.4.tar.gz | 12.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| safdie-2.0.4-py2.py3-none-any.whl | Python 3, Python 2 | none | any | Details |
Total release size: 19.7 kB
Release files / safdie-2.0.4.tar.gz
| Download URL | safdie-2.0.4.tar.gz |
|---|---|
| Size | 12.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
19063536ebf00060e4bac636d4028055ab86efad35682ee90f3e969bbda2e3f0
|
|
BLAKE2b-256 checksum How to use checksums |
08425cfe15deb32400423cdf223878263e985f0b5eb9c267d0939d1f47217cef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.9.21
|
Release files / safdie-2.0.4-py2.py3-none-any.whl
| Download URL | safdie-2.0.4-py2.py3-none-any.whl |
|---|---|
| Size | 7.6 kB |
| Tags | Python 2 Python 3 |
|
SHA-256 checksum How to use checksums |
3ee15a95cd9778f6800647302ab921fb54c2cde3b955e569f173d66a373e1a9a
|
|
BLAKE2b-256 checksum How to use checksums |
12acdd3d148d692197ca0041bd8acec163b9f38abaaa01a7279276e282620f70
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.9.21
|