Callbacks
In arc, callbacks are a way to encapsulate shared functionality across multiple commands.
In short, callbacks are:
- Reusable
- Composable
- Modular
Creating Callbacks¶
Callbacks are created using the arc.callback.create()
function. This will wrap your provided function into a callback, which you then decorate a command with to register that callback to that command.
import arc
from arc import callback
@callback.create()
def cb(args, ctx):
print("before execution")
yield
print("after execution")
@cb
@arc.command()
def command():
print("command execution")
command()
You can think of callbacks as wrapping the execution of a command. They can be used to perform any shared setup-code and any shared teardown code needed.
Tip
The examples used in this document will generally yield
to show that callbacks wrap the
execution of a command. However, they are not required to do this and can just be simple functions
if they only need to perform setup before the command runs.
Callback Creation Shorthand¶
All Command
objects have a callback()
method, so they above can be modified to:
import arc
@arc.command()
def command():
print("command execution")
@command.callback()
def callback(args: dict, ctx: arc.Context):
print("before execution")
yield
print("after execution")
command()
Command
being referred to.
Callback Inheritance¶
By default, When using arc.CLI()
, callbacks are inherited by any subcommand of the command it is added to. For Example:
from arc import CLI
cli = CLI()
@cli.callback()
def global_callback(args, ctx):
print("global callback -- start")
yield
print("global callback -- end")
@cli.command()
def c1():
print("c1")
@c1.callback()
def c1_callback(args, ctx):
print("c1 callback -- start")
yield
print("c1 callback -- end")
@c1.subcommand()
def sub():
print("c1:sub")
@cli.command()
def c2():
print("c2")
cli()
$ python callback_inherit.py c2
global callback -- start
c2
global callback -- end
$ python callback_inherit.py c1
global callback -- start
c1 callback -- start
c1
c1 callback -- end
global callback -- end
$ python callback_inherit.py c1:sub
global callback -- start
c1 callback -- start
c1:sub
c1 callback -- end
global callback -- end
c2
Executed the global callback because it inherited it from theCLI
c1
executed the global callback because it inherited it from theCLI
and it executed it's own callbackc1:sub
executed the global callback because it inherited it from theCLI
and it executed it's parent's (c1
) callback.
Callback inheritance can be stopped in two ways.
- Set
inherit = False
when creating the callback. When a callback is non-inhertiable, it can still be added to any command by deocrating the command with it. - Use
@callback.remove()
or@arc.callback.remove(*callbacks)
to remove a callback from a particular command and any of it's subcommands.
Let's modify the above example to remove all callbacks from c1:sub
from arc import CLI
cli = CLI()
@cli.callback()
def global_callback(args, ctx):
print("global callback -- start")
yield
print("global callback -- end")
@cli.command()
def c1():
print("c1")
@c1.callback(inherit=False)
def c1_callback(args, ctx):
print("c1 callback -- start")
yield
print("c1 callback -- end")
@global_callback.remove
@c1.subcommand()
def sub():
print("c1:sub")
@cli.command()
def c2():
print("c2")
cli()
$ python callback_inherit2.py c2
global callback -- start
c2
global callback -- end
$ python callback_inherit2.py c1
global callback -- start
c1
global callback -- end
$ python callback_inherit2.py c1:sub
c1:sub
c2
executed the global callback because it inherited it from theCLI
c1
executed the global callback because it inherited it from theCLI
and it executed it's own callbackc1:sub
executed neither callback because it removed theCLI
callback with@global_callback.remove
andc1_callback
is non-inhertiable.
Error Handling¶
If you're going to be performing cleanup within a callback, it's generally advisable to wrap it in a finally
block. This makes sure that the code is ran, regardless of whether or not the executed command succeeds.
You can also catch and handle exception within callbacks, but that is primarily the responsibilty of error handlers