Completed
Push — master ( 6fd567...f65269 )
by Daniel
90:27 queued 15s
created

CommandsListPlugin.method()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 5
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
dl 5
loc 5
rs 9.4285
1
import logging
2
import click
3
4
from groundwork.patterns.gw_base_pattern import GwBasePattern
5
6
7
class GwCommandsPattern(GwBasePattern):
8
    """
9
    Adds a commandline interface to a groundwork app and allows plugins to register own commands.
10
11
    The functionality is based on click: http://click.pocoo.org/5/
12
13
    To register command parameters, you have to create instances of click.Option or click.Argument manually and
14
      add them to the register-parameter "params"
15
16
    Example ::
17
18
        from groundwork import GwCommandsPattern
19
        from click import Option
20
21
        class MyPlugin(GwCommandsPattern)
22
23
            def activate(self):
24
                self.commands.register(command="my_command",
25
                                       description="Help for my command",
26
                                       params=[Option(("--test", "-t"), help="Some
27
                                       dummy text")])
28
29
            def my_command(self, my_test):
30
                    print("Command executed! my_test=%s" % my_test)
31
32
    For a complete list of configurable options, please take a look into the related click documentation of
33
    `Option
34
    <https://github.com/pallets/click/blob/c8e21105ebeb824c06c929bdd74c41eed776e956/click/core.py#L1419>`_ and
35
    `Argument <https://github.com/pallets/click/blob/c8e21105ebeb824c06c929bdd74c41eed776e956/click/core.py#L1687>`_
36
37
    **Starting the command line interface**
38
39
    Groundwork does not start automatically the command line interface. This step must be done by the application
40
    developer. Example ::
41
42
        from groundwork import GwApp
43
44
        gw_app = GwApp(plugins=["MyCommandPlugin"])
45
        gw_app.activate(plugins=["MyCommandPlugin"])
46
        gw_app.commands.start_cli()
47
    """
48
49
    def __init__(self, *args, **kwargs):
50
        super().__init__(*args, **kwargs)
51
        if not hasattr(self.app, "commands"):
52
            self.app.commands = CommandsListApplication(self.app)
53
54
        #: Instance of :class:`~.CommandsListPlugin`.
55
        #: Provides functions to register and manage commands for a command line interface.
56
        self.commands = CommandsListPlugin(self)
57
58
59
class CommandsListPlugin:
60
    def __init__(self, plugin):
61
        self.plugin = plugin
62
        self.app = plugin.app
63
        self.log = plugin.log
64
65
        # Let's register a receiver, which cares about the deactivation process of commands for this plugin.
66
        # We do it after the original plugin deactivation, so we can be sure that the registered function is the last
67
        # one which cares about commands for this plugin.
68
        self.plugin.signals.connect(receiver="%s_command_deactivation" % self.plugin.name,
69
                                    signal="plugin_deactivate_post",
70
                                    function=self.__deactivate_commands,
71
                                    description="Deactivate commands for %s" % self.plugin.name,
72
                                    sender=self.plugin)
73
        self.log.debug("Plugin commands initialised")
74
75
    def __deactivate_commands(self, plugin, *args, **kwargs):
76
        commands = self.get()
77
        for command in commands.keys():
78
            self.unregister(command)
79
80
    def register(self, command, description, function, params=[]):
81
        """
82
        Registers a new command for a plugin.
83
84
        :param command: Name of the command
85
        :param description: Description of the command. Is used as help message on cli
86
        :param function: function reference, which gets invoked if command gets called.
87
        :param params: list of click options and arguments
88
        :return: command object
89
        """
90
        return self.app.commands.register(command, description, function, params, self.plugin)
91
92
    def unregister(self, command):
93
        """
94
        Unregisters an existing command, so that this command is no longer available on the command line interface.
95
        This function is mainly used during plugin deactivation.
96
97
        :param command: Name of the command
98
        """
99
        return self.app.commands.unregister(command)
100
101
    def get(self, name=None):
102
        """
103
        Returns commands, which can be filtered by name.
104
105
        :param name: name of the command
106
        :type name: str
107
        :return: None, single command or dict of commands
108
        """
109
        return self.app.commands.get(name, self.plugin)
110
111
112
class CommandsListApplication():
113
    def __init__(self, app):
114
        self.app = app
115
        self.log = logging.getLogger(__name__)
116
        self._commands = {}
117
        self.log.info("Application commands initialised")
118
        self._click_root_command = click.Group()
119
        # self.start_cli = self._click_root_command
120
121
    def start_cli(self, *args, **kwargs):
122
        """
123
        Start the command line interface for the application.
124
125
        :param args: arguments
126
        :param kwargs: keyword arguments
127
        :return: none
128
        """
129
        return self._click_root_command(*args, **kwargs)
130
131
    def get(self, name=None, plugin=None):
132
        """
133
        Returns commands, which can be filtered by name or plugin.
134
135
        :param name: name of the command
136
        :type name: str
137
        :param plugin: plugin object, which registers the commands
138
        :type plugin: instance of GwBasePattern
139
        :return: None, single command or dict of commands
140
        """
141
        if plugin is not None:
142
            if name is None:
143
                command_list = {}
144 View Code Duplication
                for key in self._commands.keys():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
145
                    if self._commands[key].plugin == plugin:
146
                        command_list[key] = self._commands[key]
147
                return command_list
148
            else:
149
                if name in self._commands.keys():
150
                    if self._commands[name].plugin == plugin:
151
                        return self._commands[name]
152
                    else:
153
                        return None
154
                else:
155
                    return None
156
        else:
157
            if name is None:
158
                return self._commands
159
            else:
160
                if name in self._commands.keys():
161
                    return self._commands[name]
162
                else:
163
                    return None
164
165
    def register(self, command, description, function, params=[], plugin=None):
166
        """
167
        Registers a new command, which can be used on a command line interface (cli).
168
169
        :param command: Name of the command
170
        :param description: Description of the command. Is used as help message on cli
171
        :param function: function reference, which gets invoked if command gets called.
172
        :param params: list of click options and arguments
173
        :param plugin: the plugin, which registered this command
174
        :return: command object
175
        """
176
        if command in self._commands.keys():
177
            raise CommandExistException("Command %s already registered by %s" % (command,
178
                                                                                 self._commands[command].plugin.name))
179
180
        new_command = Command(command, description, params, function, plugin)
181
        self._commands[command] = new_command
182
        self._click_root_command.add_command(new_command.click_command)
183
        self.log.debug("Command registered: %s" % command)
184
        return new_command
185
186
    def unregister(self, command):
187
        """
188
        Unregisters an existing command, so that this command is no longer available on the command line interface.
189
190
        This function is mainly used during plugin deactivation.
191
192
        :param command: Name of the command
193
        """
194
        if command not in self._commands.keys():
195
            self.log.warning("Can not unregister command %s" % command)
196
        else:
197
            # Click does not have any kind of a function to unregister/remove/deactivate already added commands.
198
            # So we need to delete the related objects manually from the click internal commands dictionary for
199
            # our root command.
200
            del(self._click_root_command.commands[command])
201
            # Finally lets delete the command from our internal dictionary too.
202
            del(self._commands[command])
203
            self.log.debug("Command %s got unregistered" % command)
204
205
206
class Command:
207
    def __init__(self, command, description, parameters, function, plugin):
208
        self.command = command
209
        self.description = description
210
        self.parameters = parameters
211
        self.plugin = plugin
212
        self.function = function
213
        self.click_command = click.Command(command, callback=function, help=description, params=parameters)
214
215
216
class CommandExistException(BaseException):
217
    pass
218