Middleware
Middleware wraps command handler dispatch. A middleware stack is a series of functions, each calling the next, until the innermost handler function runs. Outermost middleware is added first and called first.
Middleware is useful for cross-cutting concerns: logging, timing, error
handling, authentication, verbosity flags. Because middleware functions live
on a normal Python call stack, you get try/except, early return, and
all other control flow for free.
Basic middleware
A middleware function is decorated with face.face_middleware(). Its
first parameter must be named next_. Call next_() to continue
execution down the chain. If you return without calling next_(), the
handler (and any downstream middleware) will not run.
import time
from face import face_middleware, echo
@face_middleware
def timing_middleware(next_):
start_time = time.time()
ret = next_()
echo('command executed in:', time.time() - start_time, 'seconds')
return ret
Add middleware to a face.Command with add():
from face import Command
cmd = Command(my_handler)
cmd.add(timing_middleware)
Middleware has the same dependency injection as handler functions. Flags and
builtins (flags_, args_, subcmds_, etc.) are automatically
available as parameters.
Providing values
Middleware can provide values to downstream middleware and handlers via
dependency injection. Declare what the middleware provides with the
provides argument, then pass the values as keyword arguments to
next_().
import time
from face import face_middleware, echo
@face_middleware(provides=['start_time'])
def timing_middleware(next_):
start_time = time.time()
ret = next_(start_time=start_time)
echo('command executed in:', time.time() - start_time, 'seconds')
return ret
Any handler or downstream middleware that accepts a start_time parameter
will receive the value automatically:
def my_handler(start_time):
# start_time injected by timing_middleware
print('started at', start_time)
Middleware with flags
Middleware can declare its own flags. These flags are automatically added to
any Command that uses the middleware. Flag values are injected
into the middleware function like any other dependency.
import time
from face import face_middleware, Flag, echo
@face_middleware(provides=['start_time'], flags=[Flag('--echo-time', parse_as=True)])
def timing_middleware(next_, echo_time):
start_time = time.time()
ret = next_(start_time=start_time)
if echo_time:
echo('command executed in:', time.time() - start_time, 'seconds')
return ret
Every Command that adds timing_middleware will gain the
--echo-time flag. The flag value is injected into echo_time by name.
Optional middleware
Set optional=True to skip middleware when none of its provides are
needed by the handler or other middleware in the chain.
@face_middleware(provides=['start_time'], optional=True)
def timing_middleware(next_):
start_time = time.time()
ret = next_(start_time=start_time)
return ret
If the resolved handler does not accept start_time, this middleware is
removed from the chain entirely. This keeps overhead and help output minimal
for commands that do not use the provided values.
Weak dependencies
Middleware parameters with default values create “weak” dependencies. If no downstream function (handler or other middleware) needs the associated injectable, the corresponding flag will not be parsed or shown in help output.
@face_middleware(provides=['start_time'], flags=[Flag('--echo-time', parse_as=True)])
def timing_middleware(next_, echo_time=False):
start_time = time.time()
ret = next_(start_time=start_time)
if echo_time:
echo('command executed in:', time.time() - start_time, 'seconds')
return ret
Here echo_time defaults to False. If no downstream function accepts
echo_time, the --echo-time flag will not appear in generated help.
This differs from handler functions, which always accept their declared
arguments regardless of defaults.
API Reference
- face.face_middleware(func: Callable | None = None, *, provides: List[str] | str = [], flags: List[Flag] = [], optional: bool = False) Callable[source]
A decorator to mark a function as face middleware, which wraps execution of a subcommand handler function. This decorator can be called with or without arguments:
- Parameters:
provides – An optional list of names, declaring which values be provided by this middleware at execution time.
flags – An optional list of Flag instances, which will be automatically added to any Command which adds this middleware.
optional – Whether this middleware should be skipped if its provides are not required by the command.
The first argument of the decorated function must be named
next_. This argument is a function, representing the next function in the execution chain, the last of which is the command’s handler function.- Returns:
A decorator function that marks the decorated function as middleware.
- face.middleware.is_middleware(target)[source]
Mostly for internal use, this function returns True if target is a valid face middleware.
Middlewares can be functions wrapped with the
face_middleware()decorator, or instances of a user-created type, as long as it’s a callable following face’s signature convention and has theis_face_middlewareattribute set to True.