Command

face.Command is the central type in face. It wraps a Parser with handler dispatch, middleware, help generation, and dependency injection. Build a CLI by creating a Command, adding flags and subcommands, then calling run().

Construction

The minimal Command takes a handler function:

from face import Command

def greet():
    print('hello')

cmd = Command(greet)
cmd.run()

name defaults to the function name (greet above). doc defaults to the first line of the function’s docstring. Override either explicitly:

def greet():
    """Say hello to the world.

    This longer description is ignored by face.
    """
    print('hello')

cmd = Command(greet, name='hi', doc='A greeting command')

Pass flags, positional arg specs, and middlewares at construction time:

from face import Command, Flag

def serve(host, port):
    print(f'Serving on {host}:{port}')

cmd = Command(
    serve,
    name='serve',
    flags=[
        Flag('--host', missing='localhost'),
        Flag('--port', parse_as=int, missing=8080),
    ],
)

See Positional arguments below and Middleware for those constructor parameters.

Adding flags

The most common way to add flags is through add() with a flag string as the first argument:

cmd = Command(handler)
cmd.add('--verbose', parse_as=True, doc='enable verbose output')
cmd.add('--output', char='-o', missing='out.txt', doc='output file path')
cmd.add('--count', parse_as=int, missing=1, doc='repetition count')

parse_as controls how the flag’s argument is parsed:

  • parse_as=str (default): flag takes one string argument.

  • parse_as=int, parse_as=float, or any callable: flag takes one argument, converted by the callable.

  • parse_as=True (or any non-callable): flag takes no argument. When present, the flag yields that value.

missing controls the value when the flag is absent:

  • missing=None (default): flag is optional, None when absent.

  • missing=ERROR: flag is required. Parse fails if absent.

  • missing=<value>: flag is optional, uses this default when absent.

multi controls behavior when a flag appears more than once:

  • multi='error' (default): raises DuplicateFlag.

  • multi='extend' or multi=True: collects all values into a list.

  • multi='override': last value wins.

char sets a short alias (e.g., char='-v').

Required flags

Use face.ERROR as the missing value:

from face import Command, ERROR

def deploy(target):
    print(f'deploying to {target}')

cmd = Command(deploy)
cmd.add('--target', missing=ERROR, doc='deploy target (required)')

ListParam and ChoicesParam

face.ListParam parses comma-separated (or other delimiter) values into a list. face.ChoicesParam restricts input to a fixed set.

from face import Command, ListParam, ChoicesParam

def tag(tags, env):
    print(f'tagging {env} with {tags}')

cmd = Command(tag)
cmd.add('--tags', parse_as=ListParam(str), doc='comma-separated tags')
cmd.add('--env', parse_as=ChoicesParam(['dev', 'staging', 'prod']),
        missing='dev', doc='target environment')

Adding subcommands

Pass a callable to add() to create a subcommand:

from face import Command, echo

def add(posargs_):
    echo(str(sum(int(x) for x in posargs_)))

def mul(posargs_):
    result = 1
    for x in posargs_:
        result *= int(x)
    echo(str(result))

def main():
    cmd = Command(None, name='calc')
    cmd.add(add, posargs=True)
    cmd.add(mul, posargs=True)
    cmd.run()

name defaults to the function name. Override with name='sub-name'.

You can also add a pre-built Command instance:

sub = Command(handler, name='sub')
sub.add('--flag', parse_as=True)

root = Command(None, name='app')
root.add(sub)

Subcommands nest arbitrarily. A subcommand is itself a Command, so it can have its own subcommands, flags, and middleware.

Subcommand groups

For commands with many subcommands, organize them under named headings in help output using CommandGroup:

from face import Command, CommandGroup

cmd = Command(None, name='myapp')

# Ungrouped subcommands appear first in help
cmd.add(version)

# Grouped subcommands appear under headings
users = CommandGroup('Users')
users.add(create_user)
users.add(delete_user)
cmd.add(users)

admin = CommandGroup('Admin')
admin.add(list_logs)
cmd.add(admin)

This produces help output like:

Subcommands:

  version        show the version

  Users:
    create-user    create a new user
    delete-user    remove a user

  Admin:
    list-logs      display application logs

A group keyword argument can also be passed directly to add() for convenience:

cmd.add(create_user, group='Users')

Positional arguments

Control positional argument handling with the posargs parameter:

  • posargs=True: accept any number of string positional args.

  • posargs=False or omitted: no positional args allowed.

  • posargs=int: accept exactly that many positional args.

  • posargs=str: sets the display name and provides name.

  • posargs=callable: accept any number, each parsed with the callable.

  • posargs=dict: keyword arguments forwarded to PosArgSpec.

  • posargs=PosArgSpec(...): full control over parsing and display.

Post-positional arguments (after --) are controlled by post_posargs, which accepts the same forms.

Adding middleware

Add middleware decorated with face.face_middleware():

from face import Command, face_middleware

@face_middleware(provides=['db'])
def provide_db(next_):
    db = connect_db()
    try:
        next_(db=db)
    finally:
        db.close()

cmd = Command(handler)
cmd.add(provide_db)

Both cmd.add(mw) and cmd.add_middleware(mw) work. add_middleware also accepts undecorated callables (it wraps them automatically).

Middleware flags are automatically added to any Command that uses the middleware. See Middleware for full details.

Running

Call run() to parse sys.argv and dispatch:

cmd.run()

Pass explicit arguments for testing or embedding:

cmd.run(argv=['myapp', '--verbose', 'subcommand', 'arg1'])

run() parses arguments, resolves the subcommand path, builds the middleware chain, and calls the handler with injected dependencies.

For pre-validation of all subcommand paths without executing, call prepare():

cmd.prepare()  # raises if any subcommand has unmet dependencies

run() only validates the specific subcommand invoked. Call prepare() after all flags, subcommands, and middlewares are added to catch configuration errors early.

Dependency injection

Handler functions and middleware receive arguments by parameter name. Face inspects the function signature and injects matching values automatically. There are two categories of injectables: flags and builtins.

Flag values are injected by the flag’s name attribute (derived from the flag string, e.g., --output-file becomes output_file).

Builtin injectables are always available:

args_

The CommandParseResult instance containing all parsed data.

cmd_

String of the command name (argv[0]).

subcmds_

Tuple of subcommand name strings for the matched path.

flags_

OrderedDict mapping flag name to parsed value.

posargs_

Tuple of positional argument strings.

post_posargs_

Tuple of post-positional argument strings (after --).

command_

The root Command instance.

subcommand_

The specific subparser (Parser) for the matched subcommand path. Same as command_ when no subcommand is used.

A handler can request any combination of these:

from face import Command, echo

def status(verbose, flags_, subcmds_):
    echo(f'subcommands: {subcmds_}')
    echo(f'verbose: {verbose}')
    echo(f'all flags: {dict(flags_)}')

cmd = Command(status)
cmd.add('--verbose', parse_as=True)

Parameters not matching any flag or builtin name cause a NameError at prepare() or run() time.

Error handling

face.CommandLineError is raised on parse failures (bad flags, missing required flags, invalid subcommands). It is a subclass of both FaceException and SystemExit, so uncaught it exits the process with a nonzero status code.

face.UsageError is for handler-level validation errors. Raise it from your handler or middleware to signal incorrect usage with a message:

from face import Command, UsageError

def deploy(target):
    if '/' in target:
        raise UsageError('--target must not contain slashes')
    print(f'deploying to {target}')

cmd = Command(deploy)
cmd.add('--target', missing='prod')

UsageError is also a SystemExit subclass, so it exits cleanly when uncaught.

API reference

class face.Command(func: ~typing.Callable | None, name: str | None = None, doc: str | None = None, *, flags: ~typing.List[~face.parser.Flag] | None = None, posargs: bool | ~face.parser.PosArgSpec | None = None, post_posargs: bool | None = None, flagfile: bool = True, help: bool | ~face.helpers.HelpHandler = <face.helpers.HelpHandler object>, middlewares: ~typing.List[~typing.Callable] | None = None, group: str | None = None)[source]

The central type in the face framework. Instantiate a Command, populate it with flags and subcommands, and then call command.run() to execute your CLI.

Parameters:
  • func – The function called when this command is run with an argv that contains no subcommands.

  • name – The name of this command, used when this command is included as a subcommand. (Defaults to name of function)

  • doc – A description or message that appears in various help outputs.

  • flags – A list of Flag instances to initialize the Command with. Flags can always be added later with the .add() method.

  • posargs – Pass True if the command takes positional arguments. Defaults to False. Can also pass a PosArgSpec instance.

  • post_posargs – Pass True if the command takes additional positional arguments after a conventional ‘–’ specifier.

  • help – Pass False to disable the automatically added –help flag. Defaults to True. Also accepts a HelpHandler instance.

  • middlewares – A list of @face_middleware decorated callables which participate in dispatch.

  • group – An optional string group name for display in help output. See CommandGroup for the recommended way to group multiple subcommands.

add(*a, **kw)[source]

Add a flag, subcommand, or middleware to this Command.

If the first argument is a callable, this method contructs a Command from it and the remaining arguments, all of which are optional. See the Command docs for for full details on names and defaults.

If the first argument is a string, this method constructs a Flag from that flag string and the rest of the method arguments, all of which are optional. See the Flag docs for more options.

If the argument is already an instance of Flag or Command, an exception is only raised on conflicting subcommands and flags. See add_command for details.

Middleware is only added if it is already decorated with @face_middleware. Use .add_middleware() for automatic wrapping of callables.

add_command(subcmd)[source]

Add a Command, and all of its subcommands, as a subcommand of this Command.

Middleware from the current command is layered on top of the subcommand’s. An exception may be raised if there are conflicting middlewares or subcommand names.

add_command_group(group)[source]

Add all commands from a CommandGroup as direct subcommands of this Command, tagged with the group’s name for organized help display.

No new subcommand path level is created — the group’s commands become siblings of any existing subcommands.

add_middleware(mw)[source]

Add a single middleware to this command. Outermost middleware should be added first. Remember: first added, first called.

get_dep_names(path=())[source]

Get a list of the names of all required arguments of a command (and any associated middleware).

By specifying path, the same can be done for any subcommand.

get_flag_map(path=(), with_hidden=True)[source]

Command’s get_flag_map differs from Parser’s in that it filters the flag map to just the flags used by the endpoint at the associated subcommand path.

prepare(paths=None)[source]

Compile and validate one or more subcommands to ensure all dependencies are met. Call this once all flags, subcommands, and middlewares have been added (using .add()).

This method is automatically called by .run() method, but it only does so for the specific subcommand being invoked. More conscientious users may want to call this method with no arguments to validate that all subcommands are ready for execution.

run(argv=None, extras=None, print_error=None)[source]

Parses arguments and dispatches to the appropriate subcommand handler. If there is a parse error due to invalid user input, an error is printed and a CommandLineError is raised. If not caught, a CommandLineError will exit the process, typically with status code 1. Also handles dispatching to the appropriate HelpHandler, if configured.

Defaults to handling the arguments on the command line (sys.argv), but can also be explicitly passed arguments via the argv parameter.

Parameters:
  • argv (list) – A sequence of strings representing the command-line arguments. Defaults to sys.argv.

  • extras (dict) – A map of additional arguments to be made available to the subcommand’s handler function.

  • print_error (callable) – The function that formats/prints error messages before program exit on CLI errors.

Note

For efficiency, run() only checks the subcommand invoked by argv. To ensure that all subcommands are configured properly, call prepare().

Command Exception Types

In addition to all the Parser-layer exceptions, a command or user endpoint function can raise:

class face.CommandLineError(msg, code=1)[source]

A FaceException and SystemExit subtype that enables safely catching runtime errors that would otherwise cause the process to exit.

If instances of this exception are left uncaught, they will exit the process.

If raised from a run() call and print_error is True, face will print the error before reraising. See face.Command.run() for more details.

class face.UsageError(msg, code=1)[source]

Application developers should raise this CommandLineError subtype to indicate to users that the application user has used the command incorrectly.

Instead of printing an ugly stack trace, Face will print a readable error message of your choosing, then exit with a nonzero exit code.