1#----------------------------------------------------------------------------- 2# Copyright (c) 2012 - 2021, Anaconda, Inc., and Bokeh Contributors. 3# All rights reserved. 4# 5# The full license is in the file LICENSE.txt, distributed with this software. 6#----------------------------------------------------------------------------- 7''' Provides a base class for defining subcommands of the Bokeh command 8line application. 9 10''' 11 12#----------------------------------------------------------------------------- 13# Boilerplate 14#----------------------------------------------------------------------------- 15import logging # isort:skip 16log = logging.getLogger(__name__) 17 18#----------------------------------------------------------------------------- 19# Imports 20#----------------------------------------------------------------------------- 21 22# Standard library imports 23from abc import ABCMeta, abstractmethod 24from argparse import ArgumentParser, Namespace 25from typing import Union 26 27#----------------------------------------------------------------------------- 28# Globals and constants 29#----------------------------------------------------------------------------- 30 31__all__ = ( 32 'Subcommand', 33) 34 35#----------------------------------------------------------------------------- 36# General API 37#----------------------------------------------------------------------------- 38 39#----------------------------------------------------------------------------- 40# Dev API 41#----------------------------------------------------------------------------- 42 43class Subcommand(metaclass=ABCMeta): 44 ''' Abstract base class for subcommands 45 46 Subclasses should implement an ``invoke(self, args)`` method that accepts 47 a set of argparse processed arguments as input. 48 49 Subclasses should also define the following class attributes: 50 51 * ``name`` a name for this subcommand 52 53 * ``help`` a help string for argparse to use for this subcommand 54 55 * ``args`` the parameters to pass to ``parser.add_argument`` 56 57 The format of the ``args`` should be a sequence of tuples of the form: 58 59 .. code-block:: python 60 61 ('argname', dict( 62 metavar='ARGNAME', 63 nargs='+', 64 )) 65 66 Example: 67 68 A simple subcommand "foo" might look like this: 69 70 .. code-block:: python 71 72 class Foo(Subcommand): 73 74 name = "foo" 75 help = "performs the Foo action" 76 args = ( 77 ('--yell', dict( 78 action='store_true', 79 help="Make it loud", 80 )), 81 ) 82 83 def invoke(self, args): 84 if args.yell: 85 print("FOO!") 86 else: 87 print("foo") 88 89 Then executing ``bokeh foo --yell`` would print ``FOO!`` at the console. 90 91 ''' 92 93 # specifying static typing of instance attributes 94 # see https://stackoverflow.com/a/51191130 95 name: str 96 help: str 97 98 def __init__(self, parser: ArgumentParser) -> None: 99 ''' Initialize the subcommand with its parser 100 101 Args: 102 parser (Parser) : an Argparse ``Parser`` instance to configure 103 with the args for this subcommand. 104 105 This method will automatically add all the arguments described in 106 ``self.args``. Subclasses can perform any additional customizations 107 on ``self.parser``. 108 109 ''' 110 self.parser = parser 111 args = getattr(self, 'args', ()) 112 for arg in args: 113 flags = arg[0] 114 if not isinstance(flags, tuple): 115 flags = (flags,) 116 self.parser.add_argument(*flags, **arg[1]) 117 118 @abstractmethod 119 def invoke(self, args: Namespace) -> Union[bool, None]: 120 ''' Takes over main program flow to perform the subcommand. 121 122 *This method must be implemented by subclasses.* 123 subclassed overwritten methods return different types: 124 bool: Build 125 None: FileOutput (subclassed by HTML, SVG and JSON. PNG overwrites FileOutput.invoke method), Info, Init, \ 126 Sampledata, Secret, Serve, Static 127 128 129 Args: 130 args (argparse.Namespace) : command line arguments for the subcommand to parse 131 132 Raises: 133 NotImplementedError 134 135 ''' 136 raise NotImplementedError("implement invoke()") 137 138#----------------------------------------------------------------------------- 139# Private API 140#----------------------------------------------------------------------------- 141 142#----------------------------------------------------------------------------- 143# Code 144#----------------------------------------------------------------------------- 145