Skip to content

Extension plugins #1040

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d54d435
add Application plugin type
davidlatwe Mar 10, 2021
0ab486d
register subcommand from application plugin
davidlatwe Mar 10, 2021
bc20be6
allow plugin installed directly with config
davidlatwe Mar 10, 2021
bef5198
add plugin_module for loading rezplugins form module
davidlatwe Mar 10, 2021
9bfa53f
revert bc20be67 for plugin_module
davidlatwe Mar 11, 2021
6d8adc3
remove redundant hierarchy
davidlatwe Mar 11, 2021
51a735a
add docstring
davidlatwe Mar 12, 2021
0b5be5c
update test
davidlatwe Mar 12, 2021
68db989
enable completer finding plugin subcommand
davidlatwe Mar 14, 2021
2ec21f7
rename 'application' to 'extension'
davidlatwe Mar 16, 2021
5acd7f9
update test
davidlatwe Mar 16, 2021
765fffb
auto-load extensions via pkgutil.iter_modules
davidlatwe Mar 16, 2021
0522dd9
fix #960
davidlatwe Mar 22, 2021
72aba0d
fix #960 - add prefix
davidlatwe Mar 22, 2021
e8edcd5
revert fix #960 for future PR
davidlatwe Mar 23, 2021
f5ad0fe
rename new plugin type to 'command'
davidlatwe Mar 23, 2021
8d0af73
make 'command_behavior' as optional
davidlatwe Mar 23, 2021
f1e15ea
cosmetic
davidlatwe Mar 23, 2021
8e820b6
add warnings
davidlatwe Mar 23, 2021
2b9cd2b
avoid sys modules re-scan
davidlatwe Mar 23, 2021
19107ef
missed rename
davidlatwe Mar 23, 2021
e1739e9
add plugin_manager tests
davidlatwe Mar 23, 2021
9e95326
add example rez extension
davidlatwe Mar 23, 2021
a184fa6
fix test
davidlatwe Mar 23, 2021
9ddc412
fix test with proper package_repository reset
davidlatwe Mar 23, 2021
88b509d
Merge branch 'master' into application-plugins
davidlatwe Mar 30, 2021
c624f0e
update test
davidlatwe Mar 30, 2021
1368513
fix test data path
davidlatwe Mar 30, 2021
45d420e
Merge branch 'master' into application-plugins
davidlatwe Apr 14, 2021
ae6ba36
fix PEP8 W391
davidlatwe Apr 14, 2021
6aebd72
only include module that has `rezplugins`
davidlatwe Jun 7, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions example_extensions/hello_cmd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Welcome to install your first rez extension!

Currently, please pip install this Python package into Rez's venv, and run
```
$ rez -i
```
You should see a plugin named "world" in the plugin list.
And now you could do
```
$ rez world -h
```
to see what you could do about it.
Empty file.
5 changes: 5 additions & 0 deletions example_extensions/hello_cmd/lib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

def get_message_from_world():
from rez.config import config
message = config.plugins.command.world.message
return message
Empty file.
Empty file.
4 changes: 4 additions & 0 deletions example_extensions/hello_cmd/rezplugins/command/rezconfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

world = {
"message": "welcome to this world."
}
37 changes: 37 additions & 0 deletions example_extensions/hello_cmd/rezplugins/command/world.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Demoing Rez's command type plugin
"""
from rez.command import Command

# This attribute is optional, default behavior will be applied if not present.
command_behavior = {
"hidden": False, # (bool): default False
"arg_mode": None, # (str): "passthrough", "grouped", default None
}


def setup_parser(parser, completions=False):
parser.add_argument("-m", "--message", action="store_true",
help="Print message from world.")


def command(opts, parser=None, extra_arg_groups=None):
from hello_cmd import lib

if opts.message:
msg = lib.get_message_from_world()
print(msg)
return

print("Please use '-h' flag to see what you can do to this world !")


class WorldCommand(Command):

@classmethod
def name(cls):
return "world"


def register_plugin():
return WorldCommand
12 changes: 12 additions & 0 deletions example_extensions/hello_cmd/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from __future__ import print_function, with_statement
from setuptools import setup


setup(
name="hello_cmd",
version="1.0",
package_dir={"hello_cmd": ""},
packages=["hello_cmd",
"hello_cmd.rezplugins",
"hello_cmd.rezplugins.command"],
)
4 changes: 2 additions & 2 deletions src/rez/cli/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ def setup_parser():

# add lazy subparsers
subparser = parser.add_subparsers(dest='cmd', metavar='COMMAND')
for subcommand in subcommands:
module_name = "rez.cli.%s" % subcommand
for subcommand, data in subcommands.items():
module_name = data.get('module_name', 'rez.cli.%s' % subcommand)

subparser.add_parser(
subcommand,
Expand Down
51 changes: 51 additions & 0 deletions src/rez/cli/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,57 @@
}


def load_plugin_cmd():
"""Load subcommand from command type plugin

The command type plugin module should have attribute `command_behavior`,
and the value must be a dict if provided. For example:

# in your command plugin module
command_behavior = {
"hidden": False, # (bool): default False
"arg_mode": None, # (str): "passthrough", "grouped", default None
}

If the attribute not present, default behavior will be given.

"""
from rez.config import config
from rez.utils.logging_ import print_debug
from rez.plugin_managers import plugin_manager

ext_plugins = dict()

for plugin_name in plugin_manager.get_plugins("command"):
module = plugin_manager.get_plugin_module("command", plugin_name)

behavior = getattr(module, "command_behavior", None)
if behavior is None:
behavior = dict()

if config.debug("plugins"):
print_debug("Attribute 'command_behavior' not found in plugin "
"module %s, registering with default behavior."
% module.__name__)
try:
data = behavior.copy()
data.update({"module_name": module.__name__})
ext_plugins[plugin_name] = data

except Exception:
if config.debug("plugins"):
import traceback
from rez.vendor.six.six import StringIO
out = StringIO()
traceback.print_exc(file=out)
print_debug(out.getvalue())

return ext_plugins


subcommands.update(load_plugin_cmd())


class LazySubParsersAction(_SubParsersAction):
"""Argparse Action which calls the `setup_subparser` function provided to
`LazyArgumentParser`.
Expand Down
3 changes: 2 additions & 1 deletion src/rez/cli/complete.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ def _pop_arg(l, p):

# create parser for subcommand
from rez.backport.importlib import import_module
module_name = "rez.cli.%s" % subcommand
data = subcommands[subcommand]
module_name = data.get("module_name", "rez.cli.%s" % subcommand)
mod = import_module(module_name)
parser = argparse.ArgumentParser()
mod.setup_parser(parser, completions=True)
Expand Down
52 changes: 52 additions & 0 deletions src/rez/command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

from rez.config import config


class Command(object):
"""An interface for registering custom Rez subcommand

To register plugin and expose subcommand, the plugin module..

* MUST have a module-level docstring (used as the command help)
* MUST provide a `setup_parser()` function
* MUST provide a `command()` function
* MUST provide a `register_plugin()` function
* SHOULD have a module-level attribute `command_behavior`

For example, a plugin named 'foo' and this is the `foo.py`:

'''The docstring for command help, this is required.
'''
from rez.command import Command

command_behavior = {
"hidden": False, # optional: bool
"arg_mode": None, # optional: None, "passthrough", "grouped"
}

def setup_parser(parser, completions=False):
parser.add_argument("--hello", ...)

def command(opts, parser=None, extra_arg_groups=None):
if opts.hello:
print("world")

class CommandFoo(Command):
schema_dict = {}

@classmethod
def name(cls):
return "foo"

def register_plugin():
return CommandFoo

"""
def __init__(self):
self.type_settings = config.plugins.extension
self.settings = self.type_settings.get(self.name())

@classmethod
def name(cls):
"""Return the name of the Command and rez-subcommand."""
raise NotImplementedError
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

from rez.package_repository import PackageRepository


class MemoryPackageRepository(PackageRepository):
@classmethod
def name(cls):
return "memory"
on_test = "bar"


def register_plugin():
return MemoryPackageRepository
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

from rez.package_repository import PackageRepository


class CloudPackageRepository(PackageRepository):
@classmethod
def name(cls):
return "cloud"


def register_plugin():
return CloudPackageRepository
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

from rez.package_repository import PackageRepository


class MemoryPackageRepository(PackageRepository):
@classmethod
def name(cls):
return "memory"
on_test = "non-mod"


def register_plugin():
return MemoryPackageRepository
Loading