Skip to content

click_wrap

Simplified wrapper for 'click' python module.

Module 'click' is used as main cli handler in AYON. Addons can register their own subcommands with options. This wrapper allows to define commands and options as with 'click', but without any dependency.

Why not to use 'click' directly? Version of 'click' used in AYON is not compatible with 'click' version used in some DCCs (e.g. Houdini 20+). And updating 'click' would break other DCCs.

How to use it? If you already have cli commands defined in addon, just replace 'click' with 'click_wrap' and it should work and modify your addon's cli method to convert 'click_wrap' object to 'click' object.

Before

import click
from ayon_core.addon import AYONAddon


class ExampleAddon(AYONAddon):
    name = "example"

    def cli(self, click_group):
        click_group.add_command(cli_main)


@click.group(ExampleAddon.name, help="Example addon")
def cli_main():
    pass


@cli_main.command(help="Example command")
@click.option("--arg1", help="Example argument 1", default="default1")
@click.option("--arg2", help="Example argument 2", is_flag=True)
def mycommand(arg1, arg2):
    print(arg1, arg2)

Now

from ayon_core import click_wrap
from ayon_core.addon import AYONAddon


class ExampleAddon(AYONAddon):
    name = "example"

    def cli(self, click_group):
        click_group.add_command(cli_main.to_click_obj())


@click_wrap.group(ExampleAddon.name, help="Example addon")
def cli_main():
    pass


@cli_main.command(help="Example command")
@click_wrap.option("--arg1", help="Example argument 1", default="default1")
@click_wrap.option("--arg2", help="Example argument 2", is_flag=True)
def mycommand(arg1, arg2):
    print(arg1, arg2)

Added small enhancements: - most of the methods can be used as chained calls - functions/methods 'command' and 'group' can be used in a way that first argument is callback function and the rest are arguments for click

Example
from ayon_core import click_wrap
from ayon_core.addon import AYONAddon


class ExampleAddon(AYONAddon):
    name = "example"

    def cli(self, click_group):
        # Define main command (name 'example')
        main = click_wrap.group(
            self._cli_main, name=self.name, help="Example addon"
        )
        # Add subcommand (name 'mycommand')
        (
            main.command(
                self._cli_command, name="mycommand", help="Example command"
            )
            .option(
                "--arg1", help="Example argument 1", default="default1"
            )
            .option(
                "--arg2", help="Example argument 2", is_flag=True,
            )
        )
        # Convert main command to click object and add it to parent group
        click_group.add_command(main.to_click_obj())

    def _cli_main(self):
        pass

    def _cli_command(self, arg1, arg2):
        print(arg1, arg2)
ayon addon example mycommand --arg1 value1 --arg2

Command

Bases: object

Source code in client/ayon_core/addon/click_wrap.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
class Command(object):
    def __init__(self, func, *args, **kwargs):
        # Command function
        self._func = func
        # Command definition arguments
        self._args = args
        # Command definition kwargs
        self._kwargs = kwargs
        # Both 'options' and 'arguments' are stored to the same variable
        #   - keep order of options and arguments
        self._options = getattr(func, FUNC_ATTR_NAME, [])

    def to_click_obj(self):
        """Converts this object to click object.

        Returns:
            click.Command: Click command object.
        """
        return convert_to_click(self)

    # --- Methods for 'convert_to_click' function ---
    def get_args(self):
        """
        Returns:
            tuple: Command definition arguments.
        """
        return self._args

    def get_kwargs(self):
        """
        Returns:
            dict[str, Any]: Command definition kwargs.
        """
        return self._kwargs

    def get_func(self):
        """
        Returns:
            Function: Function to invoke on command trigger.
        """
        return self._func

    def iter_options(self):
        """
        Yields:
            tuple[str, tuple, dict]: Option type name with args and kwargs.
        """
        for item in self._options:
            yield item
    # -----------------------------------------------

    def add_option(self, *args, **kwargs):
        return self.add_option_by_type("option", *args, **kwargs)

    def add_argument(self, *args, **kwargs):
        return self.add_option_by_type("argument", *args, **kwargs)

    option = add_option
    argument = add_argument

    def add_option_by_type(self, option_name, *args, **kwargs):
        self._options.append((option_name, args, kwargs))
        return self

get_args()

Returns:

Name Type Description
tuple

Command definition arguments.

Source code in client/ayon_core/addon/click_wrap.py
139
140
141
142
143
144
def get_args(self):
    """
    Returns:
        tuple: Command definition arguments.
    """
    return self._args

get_func()

Returns:

Name Type Description
Function

Function to invoke on command trigger.

Source code in client/ayon_core/addon/click_wrap.py
153
154
155
156
157
158
def get_func(self):
    """
    Returns:
        Function: Function to invoke on command trigger.
    """
    return self._func

get_kwargs()

Returns:

Type Description

dict[str, Any]: Command definition kwargs.

Source code in client/ayon_core/addon/click_wrap.py
146
147
148
149
150
151
def get_kwargs(self):
    """
    Returns:
        dict[str, Any]: Command definition kwargs.
    """
    return self._kwargs

iter_options()

Yields:

Type Description

tuple[str, tuple, dict]: Option type name with args and kwargs.

Source code in client/ayon_core/addon/click_wrap.py
160
161
162
163
164
165
166
def iter_options(self):
    """
    Yields:
        tuple[str, tuple, dict]: Option type name with args and kwargs.
    """
    for item in self._options:
        yield item

to_click_obj()

Converts this object to click object.

Returns:

Type Description

click.Command: Click command object.

Source code in client/ayon_core/addon/click_wrap.py
130
131
132
133
134
135
136
def to_click_obj(self):
    """Converts this object to click object.

    Returns:
        click.Command: Click command object.
    """
    return convert_to_click(self)

Group

Bases: Command

Source code in client/ayon_core/addon/click_wrap.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
class Group(Command):
    def __init__(self, func, *args, **kwargs):
        super(Group, self).__init__(func, *args, **kwargs)
        # Store sub-groupd and sub-commands to the same variable
        self._commands = []

    # --- Methods for 'convert_to_click' function ---
    def iter_commands(self):
        for command in self._commands:
            yield command
    # -----------------------------------------------

    def add_command(self, command):
        """Add prepared command object as child.

        Args:
            command (Command): Prepared command object.
        """
        if command not in self._commands:
            self._commands.append(command)

    def add_group(self, group):
        """Add prepared group object as child.

        Args:
            group (Group): Prepared group object.
        """
        if group not in self._commands:
            self._commands.append(group)

    def command(self, *args, **kwargs):
        """Add child command.

        Returns:
            Union[Command, Function]: New command object, or wrapper function.
        """
        return self._add_new(Command, *args, **kwargs)

    def group(self, *args, **kwargs):
        """Add child group.

        Returns:
            Union[Group, Function]: New group object, or wrapper function.
        """
        return self._add_new(Group, *args, **kwargs)

    def _add_new(self, target_cls, *args, **kwargs):
        func = None
        if args and callable(args[0]):
            args = list(args)
            func = args.pop(0)
            args = tuple(args)

        def decorator(_func):
            out = target_cls(_func, *args, **kwargs)
            self._commands.append(out)
            return out

        if func is not None:
            return decorator(func)
        return decorator

add_command(command)

Add prepared command object as child.

Parameters:

Name Type Description Default
command Command

Prepared command object.

required
Source code in client/ayon_core/addon/click_wrap.py
195
196
197
198
199
200
201
202
def add_command(self, command):
    """Add prepared command object as child.

    Args:
        command (Command): Prepared command object.
    """
    if command not in self._commands:
        self._commands.append(command)

add_group(group)

Add prepared group object as child.

Parameters:

Name Type Description Default
group Group

Prepared group object.

required
Source code in client/ayon_core/addon/click_wrap.py
204
205
206
207
208
209
210
211
def add_group(self, group):
    """Add prepared group object as child.

    Args:
        group (Group): Prepared group object.
    """
    if group not in self._commands:
        self._commands.append(group)

command(*args, **kwargs)

Add child command.

Returns:

Type Description

Union[Command, Function]: New command object, or wrapper function.

Source code in client/ayon_core/addon/click_wrap.py
213
214
215
216
217
218
219
def command(self, *args, **kwargs):
    """Add child command.

    Returns:
        Union[Command, Function]: New command object, or wrapper function.
    """
    return self._add_new(Command, *args, **kwargs)

group(*args, **kwargs)

Add child group.

Returns:

Type Description

Union[Group, Function]: New group object, or wrapper function.

Source code in client/ayon_core/addon/click_wrap.py
221
222
223
224
225
226
227
def group(self, *args, **kwargs):
    """Add child group.

    Returns:
        Union[Group, Function]: New group object, or wrapper function.
    """
    return self._add_new(Group, *args, **kwargs)

convert_to_click(obj_to_convert)

Convert wrapped object to click object.

Parameters:

Name Type Description Default
obj_to_convert Command

Object to convert to click object.

required

Returns:

Type Description

click.Command: Click command object.

Source code in client/ayon_core/addon/click_wrap.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
def convert_to_click(obj_to_convert):
    """Convert wrapped object to click object.

    Args:
        obj_to_convert (Command): Object to convert to click object.

    Returns:
        click.Command: Click command object.
    """
    import click

    commands_queue = collections.deque()
    commands_queue.append((obj_to_convert, None))
    top_obj = None
    while commands_queue:
        item = commands_queue.popleft()
        command_obj, parent_obj = item
        if not isinstance(command_obj, Command):
            raise TypeError(
                "Invalid type '{}' expected 'Command'".format(
                    type(command_obj)
                )
            )

        if isinstance(command_obj, Group):
            click_obj = (
                click.group(
                    *command_obj.get_args(),
                    **command_obj.get_kwargs()
                )(command_obj.get_func())
            )

        else:
            click_obj = (
                click.command(
                    *command_obj.get_args(),
                    **command_obj.get_kwargs()
                )(command_obj.get_func())
            )

        for item in command_obj.iter_options():
            option_name, args, kwargs = item
            if option_name == "option":
                click.option(*args, **kwargs)(click_obj)
            elif option_name == "argument":
                click.argument(*args, **kwargs)(click_obj)
            else:
                raise ValueError(
                    "Invalid option name '{}'".format(option_name)
                )

        if top_obj is None:
            top_obj = click_obj

        if parent_obj is not None:
            parent_obj.add_command(click_obj)

        if isinstance(command_obj, Group):
            for command in command_obj.iter_commands():
                commands_queue.append((command, click_obj))

    return top_obj