cmdkit.app#

The Application class provides structure to command-line interfaces. Create a new derived application class to share the common boilerplate among all the entry-points in your project.



Reference#


class Application(**parameters)[source]#

Abstract base class for all application interfaces.

An application is typically initialized with one of the factory methods from_namespace() or from_cmdline(). These parse command-line arguments using the member Interface. Direct initialization takes named parameters and are simply assigned to the instance. These should be existing class-level attributes with annotations.


interface: Interface = None#

The command-line argument parser for this application. Calls to add_argument() should have their destination set to class-level attribute names.

class MyApp(Application):
    ...

    interface = Interface('myapp', USAGE_TEXT, HELP_TEXT)

    output: str = '-'
    interface.add_argument('-o', '--output', default=output)

    debug_mode: bool = False
    interface.add_argument('-d', '--debug', action='store_true', dest='debug_mode')

ALLOW_NOARGS: bool = False#

By default, if the cmdline list of arguments passed to main() is empty, the usage text is printed to sys.stdout and exit_status.success is returned.

If instead the application should proceed to its run method even in the absence of any arguments, set ALLOW_NOARGS as a class-level attribute.

class MyApp(Application):
    ...

    ALLOW_NOARGS = True

exceptions: Dict[Type[Exception], Callable[[Exception], int]] = {}#

Map of exceptions to catch and their associated handler. The handlers should take an Exception instance as the single argument and return an integer value as the exit status to use.

def log_and_exit(status: int, error: Exception) -> int:
    """Log the error message as critical and exit with `status`."""
    log.critical(' - '.join(error.args))
    return status

class MyApp(Application):
    ...

    exceptions = {
        ConfigurationError:
            functools.partial(log_and_exit, exit_status.bad_config),
    }

log_critical: Callable[[str], None] = <bound method Logger.critical of <Logger cmdkit.app (WARNING)>>#
log_exception: Callable[[str], None] = <bound method Logger.exception of <Logger cmdkit.app (WARNING)>>#

Bound methods used by main() to log messages. In the main try/except block, this method is called with a message. This can be overridden by any function with the same interface. It is recommended to override this with your own logger.


classmethod from_cmdline(cmdline: List[str] | None = None) TApp[source]#

Initialize via command-line arguments (e.g., sys.argv).


classmethod from_namespace(namespace: Namespace) TApp[source]#

Initialize via existing namespace/namedtuple.


classmethod main(cmdline: List[str] | None = None, shared: Namespace | None = None) int[source]#

Entry-point for application. This is a try-except block that handles standard scenarios.

See Also:

exceptions

The main method should be exposed as an entry-point in your setup.py script.

def main() -> int:
    return MyApp.main(sys.argv[1:])
setup(
    ...
    # if main() is in myapp.__init__
    entry_points = {'console_script': ['myapp=myapp:main']}
)

abstract run() None[source]#

Business-logic of the application.

class MyApp(Application):
    ...

    def run(self) -> None:
        log.info('started')

__enter__() Application[source]#

Place-holder for context manager.

The main() method will essentially call the following on your behalf.

with MyApp.from_cmdline(...) as app:
    app.run()

Placing resource acquisition code here makes it easy to ensure that the proper tear down procedures happen. If you need to open files or acquire connections, place the closing methods in __exit__().

class MyApp(Application):
    ...

    output: str = '-'
    output_file: IO = sys.stdout
    interface.add_argument('-o', '--output', default=output)

    def __enter__(self) -> MyApp:
        """Open output file if necessary."""
        if self.output != '-':
            self.output_file = open(self.output, mode='w')

    def __exit__(self, *exc) -> None:
        """Close output file if necessary."""
        if self.output != '-':
            self.output_file.close()

__exit__(*exc) None[source]#

Release resources.


shared: Namespace = None#

A shared Namespace with parameters from parent group(s). See ApplicationGroup.


classmethod handle_usage(message: str) None[source]#

Handler method invoked by main() when no arguments are given. Default implementation simply prints the interface.usage_text.

classmethod handle_help(message: str) None[source]#

Handler method invoked by main() after catching the HelpOption exception. Default implementation simply prints the interface.help_text.

classmethod handle_version(*args) None[source]#

Handler method invoked by main() after catching the VersionOption exception. Default implementation simply prints the arguments given.


class ApplicationGroup(**parameters)[source]#

A group entry-point delegates to a member Application.

class MainApp(ApplicationGroup):
    """Top-level entry-point for a hierarchical interface."""

    interface = Interface('myapp', USAGE_TEXT, HELP_TEXT)

    command: str
    interface.add_argument('command')

    commands = {
        'config': ConfigApp,
        'list': ListApp,
        'run': RunApp,
    }

shared: Namespace = None#

Shared arguments from parent ApplicationGroup.

ALLOW_PARSE: bool = False#

By default, the cmdline list passed to main() has its first argument popped and used to lookup which member Application to run.

If ALLOW_PARSE is True, then known options of the group interface are parsed from cmdline and retained in a member Namespace, Application.shared, with the remainder passed on to the down-line Application.

class MainApp(ApplicationGroup):
    ...

    ALLOW_PARSED = True

    verbose: bool = False
    interface.add_argument('--verbose', action='store_true')

exit_status = (0, 1, 2, 3, 4, 5, 6)#

Collection of exit status values.

This namespace of integer values provides structure to exit status management. It is used internally by the Application class. These values can be changed by re-assigning them.