Ticket #684: plugin_commands_#694.patch

File plugin_commands_#694.patch, 7.0 KB (added by Ben Sturmfels, 4 years ago)
  • mediagoblin/gmg_commands/__init__.py

    From efbdd8cff4e7ad7511b60462f01f44f4cb95bb70 Mon Sep 17 00:00:00 2001
    From: Ben Sturmfels <ben@sturm.com.au>
    Date: Fri, 22 Nov 2019 15:43:10 +1100
    Subject: [PATCH] Allow plugins to register custom gmg commands.
    
    This change adds a `register_commands` function to the plugins API to be called
    during plugin setup hook. The `sampleplugin` now includes a `samplecommand`.
    ---
     mediagoblin/gmg_commands/__init__.py         | 62 ++++++++++++++++----
     mediagoblin/plugins/sampleplugin/__init__.py | 20 ++++++-
     mediagoblin/tools/pluginapi.py               | 27 +++++++++
     3 files changed, 98 insertions(+), 11 deletions(-)
    
    diff --git a/mediagoblin/gmg_commands/__init__.py b/mediagoblin/gmg_commands/__init__.py
    index 0034fd98..ba5339ca 100644
    a b import shutil  
    2020
    2121import six
    2222
     23from mediagoblin.init import plugins, setup_global_and_app_config
     24from mediagoblin.tools.pluginapi import PluginManager
    2325from mediagoblin.tools.common import import_component
    2426
    2527import logging
    SUBCOMMAND_MAP = {  
    9092    }
    9193
    9294
    93 def main_cli():
     95def argparser_with_conf_file(add_help=True):
     96    """Makes an base ArgumentParser including the --conf_file option.
     97
     98    Factored out to avoid duplication caused by initial parsing before plugins
     99    are loaded.
     100
     101    """
    94102    parser = argparse.ArgumentParser(
    95         description='GNU MediaGoblin utilities.')
     103        description='GNU MediaGoblin utilities.',
     104        add_help=add_help)
    96105    parser.add_argument(
    97106        '-cf', '--conf_file', default=None,
    98107        help=(
    99108            "Config file used to set up environment.  "
    100109            "Default to mediagoblin_local.ini if readable, "
    101110            "otherwise mediagoblin.ini"))
     111    return parser
     112
     113
     114def find_conf_file(conf_file=None):
     115    """Find the config file when not explictly specified.
     116
     117    Factored out to avoid duplication caused by initial parsing before plugins
     118    are loaded.
     119
     120    TODO: Give that mediagoblin_local.ini is now deprecated, this could be
     121    removed entirely. How would we warn people that are still using
     122    mediagoblin_local.ini?
     123
     124    """
     125    if conf_file is None:
     126        if os.path.exists('mediagoblin_local.ini') \
     127                and os.access('mediagoblin_local.ini', os.R_OK):
     128            conf_file = 'mediagoblin_local.ini'
     129        else:
     130            conf_file = 'mediagoblin.ini'
     131    return conf_file
     132
    102133
     134def main_cli():
     135    # Initial argument parse to load config file and set up plugins.
     136    #
     137    # Until the config file has been read, we don't know what plugins will be
     138    # activated and what custom gmg commands they define. Here we custom
     139    # commands using "parse_known_args". The command line help is also not
     140    # useful yet, so that's disabled.
     141    initial_parser = argparser_with_conf_file(add_help=False)
     142    initial_args, _ = initial_parser.parse_known_args()
     143    initial_args.conf_file = find_conf_file(initial_args.conf_file)
     144    global_config, app_config = setup_global_and_app_config(initial_args.conf_file)
     145    plugins.setup_plugins()
     146
     147    # Full argument parse including any gmg commands from now loaded plugins.
     148    parser = argparser_with_conf_file()
    103149    subparsers = parser.add_subparsers(help='sub-command help')
    104     for command_name, command_struct in six.iteritems(SUBCOMMAND_MAP):
     150    # Merge command dicts without mutating - ugly in Python < 3.9.
     151    subcommands = {**SUBCOMMAND_MAP, **PluginManager().get_commands()}
     152    for command_name, command_struct in six.iteritems(subcommands):
    105153        if 'help' in command_struct:
    106154            subparser = subparsers.add_parser(
    107155                command_name, help=command_struct['help'])
    def main_cli():  
    116164        subparser.set_defaults(func=exec_func)
    117165
    118166    args = parser.parse_args()
    119     args.orig_conf_file = args.conf_file
    120     if args.conf_file is None:
    121         if os.path.exists('mediagoblin_local.ini') \
    122                 and os.access('mediagoblin_local.ini', os.R_OK):
    123             args.conf_file = 'mediagoblin_local.ini'
    124         else:
    125             args.conf_file = 'mediagoblin.ini'
     167    args.conf_file = initial_args.conf_file
    126168
    127169    # This is a hopefully TEMPORARY hack for adding a mediagoblin.ini
    128170    # if none exists, to make up for a deficiency as we are migrating
  • mediagoblin/plugins/sampleplugin/__init__.py

    diff --git a/mediagoblin/plugins/sampleplugin/__init__.py b/mediagoblin/plugins/sampleplugin/__init__.py
    index 2cd077a2..9f83a069 100644
    a b  
    1717
    1818import logging
    1919
    20 from mediagoblin.tools.pluginapi import get_config
     20from mediagoblin.gmg_commands import util as commands_util
     21from mediagoblin.tools.pluginapi import get_config, register_commands
    2122
    2223
    2324_log = logging.getLogger(__name__)
    def setup_plugin():  
    3637        _log.info('There is no configuration set.')
    3738    _setup_plugin_called += 1
    3839
     40    register_commands({
     41        'samplecommand': {
     42            'setup': 'mediagoblin.plugins.sampleplugin:samplecommand_parser_setup',
     43            'func': 'mediagoblin.plugins.sampleplugin:samplecommand',
     44            'help': 'Do something'},
     45        })
    3946
    4047hooks = {
    4148    'setup': setup_plugin
    4249    }
     50
     51
     52def samplecommand_parser_setup(subparser):
     53    subparser.add_argument(
     54        '--username','-u',
     55        help="Username to greet")
     56
     57
     58def samplecommand(args):
     59    args.username = commands_util.prompt_if_not_set(args.username, "Username:")
     60    print("Hello {args.username}, I'm a command!".format(args=args))
  • mediagoblin/tools/pluginapi.py

    diff --git a/mediagoblin/tools/pluginapi.py b/mediagoblin/tools/pluginapi.py
    index 1eabe9f1..9071f201 100644
    a b class PluginManager(object):  
    8888
    8989        # list of registered routes
    9090        "routes": [],
     91
     92        # dictionary of commands
     93        "commands": {},
    9194        }
    9295
    9396    def clear(self):
    class PluginManager(object):  
    146149    def get_template_hooks(self, hook_name):
    147150        return self.template_hooks.get(hook_name, [])
    148151
     152    def register_commands(self, commands):
     153        self.commands.update(commands)
     154
     155    def get_commands(self):
     156        return self.commands
     157
    149158
    150159def register_routes(routes):
    151160    """Registers one or more routes
    def get_hook_templates(hook_name):  
    274283    return PluginManager().get_template_hooks(hook_name)
    275284
    276285
     286def register_commands(commands):
     287    """
     288    Register a list of gmg commands.
     289
     290    Takes a dictionary of gmg subcommands, in the same format as mediagoblin.gmg_commands.SUBCOMMAND_MAP.
     291
     292    Example:
     293
     294    .. code-block:: python
     295
     296    {'batchaddmedia': {
     297        'setup': 'mediagoblin.gmg_commands.batchaddmedia:parser_setup',
     298        'func': 'mediagoblin.gmg_commands.batchaddmedia:batchaddmedia',
     299        'help': 'Add many media entries at once'}}
     300    """
     301    PluginManager().register_commands(commands)
     302
     303
    277304#############################
    278305## Hooks: The Next Generation
    279306#############################