Skip to content
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Brewtils Changelog
------
TBD

- Added support to auto generate requests when calling functions locally that are annotated with command decorators. This only supports
plugin classes that extend `object` and does not support recursive command loops. This is disabled by default (#547)
Examples: Plugin(..., auto_self_client=True)
- Added support for SystemClient to define choice_validation_enabled flag during initialization or command execution.
Default behavior is to skip choice validation if a parent request is present. (#585)
Examples: SystemClient(..., choice_validation_enabled=True) or SystemClient().call_command(..., _choice_validation_enabled=True)
Expand Down
29 changes: 29 additions & 0 deletions brewtils/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
)
from brewtils.resolvers.manager import ResolutionManager
from brewtils.rest.easy_client import EasyClient
from brewtils.rest.system_client import SystemClient
from brewtils.specification import _CONNECTION_SPEC
from typing import Optional, Union

Expand Down Expand Up @@ -138,6 +139,7 @@ class Plugin(object):
- ``require``
- ``requires``
- ``requires_timeout``
- ``auto_self_client`` (defaults to "false")

Connection information tells the Plugin how to communicate with Beer-garden. The
most important of these is the ``bg_host`` (to tell the plugin where to find the
Expand Down Expand Up @@ -250,6 +252,9 @@ class Plugin(object):

prefix_topic (str): Prefix for Generated Command Topics

auto_self_client (bool): Whether to automatically invoke SystemClient for local
system commands via self

logger (:py:class:`logging.Logger`): Logger that will be used by the Plugin.
Passing a logger will prevent the Plugin from preforming any additional
logging configuration.
Expand Down Expand Up @@ -283,6 +288,10 @@ def __init__(self, client=None, system=None, logger=None, **kwargs):
self._custom_logger = False
self._logger = self._setup_logging(logger=logger, **kwargs)

self._auto_self_client = kwargs.pop("auto_self_client", False)
if not isinstance(self._auto_self_client, bool):
self._auto_self_client = str(self._auto_self_client).lower() == "true"

# Need to pop out shutdown functions because these are not processed
# until shutdown
self.shutdown_functions = []
Expand Down Expand Up @@ -480,6 +489,26 @@ def _set_client(self, new_client):
client_clazz._requires = self._system.requires
client_clazz._current_request = client_clazz.current_request

# Can only inject System Clients if the Client inherits from a class object
# TODO: Should we add a configuration to explicitly disable this?
if self._auto_self_client and issubclass(client_clazz, object):

def system_client_getattribute(self, name):
try:
if not name.startswith("_"):
request = get_current_request_read_only()
# Does not support recursive calls
if request and request.command != name:
if self.__class__._bg_commands:
for bg_command in self.__class__._bg_commands:
if bg_command.name == name:
return getattr(SystemClient(), name)
except Exception:
pass
return object.__getattribute__(self, name)

new_client.__class__.__getattribute__ = system_client_getattribute

self._client = new_client
global CLIENT
CLIENT = new_client
Expand Down
31 changes: 27 additions & 4 deletions brewtils/rest/system_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,10 +414,33 @@ def send_bg_request(self, *args, **kwargs):
# tried to pass a parameter without a key:
# client.command_name(param)
if args:
raise RequestProcessException(
"Using positional arguments when creating a request is not allowed. "
"Please use keyword arguments instead."
)
if self.target_self:
_command = self._commands[kwargs["_command"]]
if _command:
arg_counter = 0
for arg in args:
if arg_counter < len(_command.parameters):
if _command.parameters[arg_counter].key in kwargs:
raise RequestProcessException(
(
"Keyword argument overlapped with provided positional "
"argument. Please use all keyword arguments instead."
)
)
kwargs[_command.parameters[arg_counter].key] = arg
arg_counter = arg_counter + 1
else:
raise RequestProcessException(
(
"More positional arguments provided that command parameters. "
"Please use all keyword arguments instead."
)
)
else:
raise RequestProcessException(
"Using positional arguments when creating a request is not allowed. "
"Please use keyword arguments instead."
)

# Need to pop here, otherwise we'll try to send as a request parameter
raise_on_error = kwargs.pop("_raise_on_error", self._raise_on_error)
Expand Down
8 changes: 8 additions & 0 deletions brewtils/specification.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ def _is_json_dict(s):
}

_PLUGIN_SPEC = {
"auto_self_client": {
"type": "bool",
"description": (
"Whether to automatically invoke SystemClient for local"
"system commands via self"
),
"default": False,
},
"instance_name": {
"type": "str",
"description": "The instance name",
Expand Down
2 changes: 2 additions & 0 deletions test/plugin_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ def test_env(self, client, bg_system):
os.environ["BG_GROUP"] = "GroupA"
os.environ["BG_PREFIX_TOPIC"] = "custom.topic"
os.environ["BG_REQUIRE"] = "SystemA"
os.environ["BG_AUTO_SELF_CLIENT"] = "False"

plugin = Plugin(client, system=bg_system, max_concurrent=1)

Expand All @@ -210,6 +211,7 @@ def test_env(self, client, bg_system):
assert plugin._config.ssl_enabled is False
assert plugin._config.ca_verify is False
assert plugin._config.prefix_topic == "custom.topic"
assert plugin._config.auto_self_client is False
assert "GroupA" == plugin._config.group
assert "SystemA" in plugin._config.require

Expand Down
Loading