Source code for bokeh.command.subcommand

#-----------------------------------------------------------------------------
# Copyright (c) 2012 - 2023, Anaconda, Inc., and Bokeh Contributors.
# All rights reserved.
#
# The full license is in the file LICENSE.txt, distributed with this software.
#-----------------------------------------------------------------------------
''' Provides a base class for defining subcommands of the Bokeh command
line application.

'''

#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------
from __future__ import annotations

import logging # isort:skip
log = logging.getLogger(__name__)

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------

# Standard library imports
from abc import ABCMeta, abstractmethod
from argparse import ArgumentParser, Namespace
from typing import (
    TYPE_CHECKING,
    Any,
    ClassVar,
    Literal,
    Sequence,
    Tuple,
    Union,
)

# Bokeh imports
from ..util.dataclasses import (
    NotRequired,
    Unspecified,
    dataclass,
    entries,
)

if TYPE_CHECKING:
    from typing_extensions import TypeAlias

#-----------------------------------------------------------------------------
# Globals and constants
#-----------------------------------------------------------------------------

__all__ = (
    'Subcommand',
)

#-----------------------------------------------------------------------------
# General API
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Dev API
#-----------------------------------------------------------------------------

@dataclass
class Argument:
    action: NotRequired[Literal["store", "store_const", "store_true", "append", "append_const", "count", "help", "version", "extend"]] = Unspecified
    nargs: NotRequired[int | Literal["?", "*", "+", "..."]] = Unspecified
    const: NotRequired[Any] = Unspecified
    default: NotRequired[Any] = Unspecified
    type: NotRequired[type[Any]] = Unspecified
    choices: NotRequired[Sequence[Any]] = Unspecified
    required: NotRequired[bool] = Unspecified
    help: NotRequired[str] = Unspecified
    metavar: NotRequired[str] = Unspecified

Arg: TypeAlias = Tuple[Union[str, Tuple[str, ...]], Argument]
Args: TypeAlias = Tuple[Arg, ...]

[docs]class Subcommand(metaclass=ABCMeta): ''' Abstract base class for subcommands Subclasses should implement an ``invoke(self, args)`` method that accepts a set of argparse processed arguments as input. Subclasses should also define the following class attributes: * ``name`` a name for this subcommand * ``help`` a help string for argparse to use for this subcommand * ``args`` the parameters to pass to ``parser.add_argument`` The format of the ``args`` should be a sequence of tuples of the form: .. code-block:: python ('argname', Argument( metavar='ARGNAME', nargs='+', )) Example: A simple subcommand "foo" might look like this: .. code-block:: python class Foo(Subcommand): name = "foo" help = "performs the Foo action" args = ( ('--yell', Argument( action='store_true', help="Make it loud", )), ) def invoke(self, args): if args.yell: print("FOO!") else: print("foo") Then executing ``bokeh foo --yell`` would print ``FOO!`` at the console. ''' name: ClassVar[str] help: ClassVar[str] args: ClassVar[Args] = ()
[docs] def __init__(self, parser: ArgumentParser) -> None: ''' Initialize the subcommand with its parser Args: parser (Parser) : an Argparse ``Parser`` instance to configure with the args for this subcommand. This method will automatically add all the arguments described in ``self.args``. Subclasses can perform any additional customizations on ``self.parser``. ''' self.parser = parser for arg in self.args: flags, spec = arg if not isinstance(flags, tuple): flags = (flags,) if not isinstance(spec, dict): kwargs = dict(entries(spec)) else: # NOTE: allow dict for run time backwards compatibility, but don't include in types kwargs = spec self.parser.add_argument(*flags, **kwargs)
[docs] @abstractmethod def invoke(self, args: Namespace) -> bool | None: ''' Takes over main program flow to perform the subcommand. *This method must be implemented by subclasses.* subclassed overwritten methods return different types: bool: Build None: FileOutput (subclassed by HTML, SVG and JSON. PNG overwrites FileOutput.invoke method), Info, Init, \ Sampledata, Secret, Serve, Static Args: args (argparse.Namespace) : command line arguments for the subcommand to parse Raises: NotImplementedError ''' raise NotImplementedError("implement invoke()")
#----------------------------------------------------------------------------- # Private API #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Code #-----------------------------------------------------------------------------