Skip to content
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

Adding deprecated to command and parameter decorators #526

Merged
merged 6 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Brewtils Changelog
------
TBD

- Added deprecated decorator and deprecated kwarg to command and parameter decorators
- Added `is_newer` support for various models to enable improved event handling in framework

3.29.1
Expand Down
29 changes: 29 additions & 0 deletions brewtils/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def command(
tag=None, # type: str
tags=None, # type: Optional[List[str]]
allow_any_kwargs=None, # type: Optional[bool]
deprecated=None, # type: Optional[bool]
):
"""Decorator for specifying Command details

Expand Down Expand Up @@ -158,6 +159,7 @@ def echo_json(self, message):
tags: A list of tags that can be used to filter commands
allow_any_kwargs: Flag controlling whether passed kwargs will be restricted to
the Command parameters defined.
deprecated: Boolean to indicate if command is deprecated

Returns:
The decorated function
Expand Down Expand Up @@ -207,6 +209,7 @@ def echo_json(self, message):
metadata=metadata,
tags=tags,
allow_any_kwargs=allow_any_kwargs,
deprecated=deprecated,
)

if output_type is None:
Expand All @@ -231,6 +234,7 @@ def echo_json(self, message):
metadata=metadata,
tags=tags,
allow_any_kwargs=allow_any_kwargs,
deprecated=deprecated,
)

# Python 2 compatibility
Expand Down Expand Up @@ -261,6 +265,7 @@ def parameter(
type_info=None, # type: Optional[dict]
is_kwarg=None, # type: Optional[bool]
model=None, # type: Optional[Type]
deprecated=None, # type: Optional[bool]
):
"""Decorator for specifying Parameter details

Expand Down Expand Up @@ -304,6 +309,7 @@ def echo(self, message):
method.
model: Class to be used as a model for this parameter. Must be a Python type
object, not an instance.
deprecated: Boolean to indicate if this parameter is deprecated

Returns:
The decorated function
Expand Down Expand Up @@ -346,6 +352,7 @@ def echo(self, message):
type_info=type_info,
is_kwarg=is_kwarg,
model=model,
deprecated=deprecated,
)

new_parameter = Parameter(
Expand All @@ -366,6 +373,7 @@ def echo(self, message):
type_info=type_info,
is_kwarg=is_kwarg,
model=model,
deprecated=deprecated,
)

# Python 2 compatibility
Expand Down Expand Up @@ -551,6 +559,11 @@ def _parse_shutdown_functions(client):
return shutdown_functions


def _parse_deprecated(method):
"""Determine if method is deprecated"""
return any("__deprecated__" in t for t in inspect.getmembers(method))


def _parse_startup_functions(client):
# type: (object) -> List[Callable]
"""Get a list of callable fields labeled with the startup annotation
Expand Down Expand Up @@ -679,6 +692,13 @@ def _initialize_command(method):
cmd.name = _method_name(method)
cmd.display_name = cmd.display_name or _method_name(method)
cmd.description = cmd.description or _method_docstring(method)
cmd.deprecated = cmd.deprecated or _parse_deprecated(method)
if cmd.deprecated:
cmd.hidden = cmd.deprecated
if cmd.description:
cmd.description = f"(Deprecated) {cmd.description}"
else:
cmd.description = "(Deprecated)"

try:
base_dir = os.path.dirname(inspect.getfile(method))
Expand Down Expand Up @@ -902,6 +922,7 @@ def _initialize_parameter(
is_kwarg=None,
model=None,
method=None,
deprecated=None,
):
# type: (...) -> Parameter
"""Initialize a Parameter
Expand Down Expand Up @@ -942,6 +963,7 @@ def _initialize_parameter(
type_info=type_info,
is_kwarg=is_kwarg,
model=model,
deprecated=deprecated,
)

# Every parameter needs a key, so stop that right here
Expand Down Expand Up @@ -969,6 +991,13 @@ def _initialize_parameter(
# Process the raw choices into a Choices object
param.choices = process_choices(param.choices)

# Process deprecated
if param.deprecated:
if param.description:
param.description = f"(Deprecated) {param.description}"
else:
param.description = "(Deprecated)"

# Now deal with nested parameters
if param.parameters or param.model:
if param.model:
Expand Down
4 changes: 4 additions & 0 deletions brewtils/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def __init__(
tags=None,
topics=None,
allow_any_kwargs=None,
deprecated=None,
):
self.name = name
self.display_name = display_name or name
Expand All @@ -159,6 +160,7 @@ def __init__(
self.tags = tags or []
self.topics = topics or []
self.allow_any_kwargs = allow_any_kwargs
self.deprecated = deprecated

def __str__(self):
return self.name
Expand Down Expand Up @@ -351,6 +353,7 @@ def __init__(
regex=None,
form_input_type=None,
type_info=None,
deprecated=None,
is_kwarg=None,
model=None,
):
Expand All @@ -369,6 +372,7 @@ def __init__(
self.regex = regex
self.form_input_type = form_input_type
self.type_info = type_info or {}
self.deprecated = deprecated

# These are special - they aren't part of the Parameter "API" (they aren't in
# the serialization schema) but we still need them on this model for consistency
Expand Down
2 changes: 2 additions & 0 deletions brewtils/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class ParameterSchema(BaseSchema):
regex = fields.Str(allow_none=True)
form_input_type = fields.Str(allow_none=True)
type_info = fields.Dict(allow_none=True)
deprecated = fields.Bool(allow_none=True)


class CommandSchema(BaseSchema):
Expand All @@ -204,6 +205,7 @@ class CommandSchema(BaseSchema):
tags = fields.List(fields.Str(), allow_none=True)
topics = fields.List(fields.Str(), allow_none=True)
allow_any_kwargs = fields.Boolean(allow_none=True)
deprecated = fields.Boolean(allow_none=True)


class InstanceSchema(BaseSchema):
Expand Down
3 changes: 3 additions & 0 deletions brewtils/test/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def nested_parameter_dict():
"regex": None,
"form_input_type": None,
"type_info": {},
"deprecated": False,
}


Expand All @@ -153,6 +154,7 @@ def parameter_dict(nested_parameter_dict, choices_dict):
"regex": ".*",
"form_input_type": None,
"type_info": {},
"deprecated": False,
}


Expand Down Expand Up @@ -184,6 +186,7 @@ def command_dict(parameter_dict, system_id):
"tags": [],
"topics": [],
"allow_any_kwargs": False,
"deprecated": False,
}


Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def find_version():
"requests<3",
"simplejson<4",
"six<2",
"typing_extensions>=4.12.2",
"wrapt",
"yapconf>=0.3.7",
],
Expand Down
141 changes: 141 additions & 0 deletions test/decorators_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
else:
from inspect import signature # noqa

if sys.version_info.major == 3 and sys.version_info.minor >= 13:
from warnings import deprecated # noqa
elif sys.version_info.major == 3 and sys.version_info.minor < 13:
from typing_extensions import deprecated # noqa


@pytest.fixture
def cmd():
Expand Down Expand Up @@ -697,6 +702,45 @@ def cmd1(foo):

assert cmd1._command.display_name == "foo_test"

def test_command_deprecated_docstring(self):
@deprecated("Do not use for this reason")
@command(deprecated=True)
def cmd():
"""Docstring"""
return True

assert hasattr(cmd, "_command")

_cmd = _initialize_command(cmd)

assert _cmd.description == "(Deprecated) Docstring"
assert _cmd.hidden is True

def test_command_deprecated_description(self):
@command(description="Description", deprecated=True)
def cmd():
"""Docstring"""
return True

assert hasattr(cmd, "_command")

_cmd = _initialize_command(cmd)

assert _cmd.description == "(Deprecated) Description"
assert _cmd.hidden is True

def test_command_deprecated_no_description(self):
@command(deprecated=True)
def cmd():
return True

assert hasattr(cmd, "_command")

_cmd = _initialize_command(cmd)

assert _cmd.description == "(Deprecated)"
assert _cmd.hidden is True


class TestParameter(object):
"""Test parameter decorator
Expand Down Expand Up @@ -796,6 +840,32 @@ def cmd_bad_1(foo):
def cmd_bad_2(foo):
return foo

def test_parameter_deprecated(self, basic_param):
@parameter(**basic_param, deprecated=True)
def cmd(foo):
return foo

assert hasattr(cmd, "parameters")
assert len(cmd.parameters) == 1
assert (
_initialize_parameter(cmd.parameters[0]).description
== "(Deprecated) Mutant"
)

def test_parameter_deprecated_multiple_param(self, basic_param):
@parameter(**basic_param)
@parameter(key="bar", description="Param2", deprecated=True)
def cmd(foo):
return foo

assert hasattr(cmd, "parameters")
assert len(cmd.parameters) == 2
assert _initialize_parameter(cmd.parameters[0]).description == "Mutant"
assert (
_initialize_parameter(cmd.parameters[1]).description
== "(Deprecated) Param2"
)


class TestParameters(object):
@pytest.fixture(autouse=True)
Expand Down Expand Up @@ -1081,6 +1151,31 @@ def test_file_defaults(self):
"""File parameter defaults should be cleared for safety"""
assert _initialize_parameter(Parameter(key="f", type="Base64")).default is None

def test_deprecated(self):
"""File parameter defaults should be cleared for safety"""
assert (
_initialize_parameter(Parameter(key="a", type="boolean")).description
is None
)
assert (
_initialize_parameter(
Parameter(key="a", type="boolean", deprecated=True)
).description
== "(Deprecated)"
)
assert (
_initialize_parameter(
Parameter(key="a", type="boolean", description="Desc")
).description
== "Desc"
)
assert (
_initialize_parameter(
Parameter(key="a", type="boolean", description="Desc", deprecated=True)
).description
== "(Deprecated) Desc"
)

class TestNesting(object):
@pytest.fixture
def inner(self):
Expand Down Expand Up @@ -1463,6 +1558,52 @@ def test_plugin_param(self, cmd, parameter_dict):
)


class TestDeprecated(object):
"""Test deprecated parameter"""

def test_deprecated_command_docstring(self):
@deprecated("Do not use for this reason")
@command
def cmd():
"""Docstring"""
return True

# Initialize this command and make sure the description has been updated
assert hasattr(cmd, "_command")

_cmd = _initialize_command(cmd)

assert _cmd.description == "(Deprecated) Docstring"
assert _cmd.hidden is True

def test_deprecated_command_description(self):
@deprecated("Do not use for this reason")
@command(description="Description")
def cmd():
"""Docstring"""
return True

assert hasattr(cmd, "_command")

_cmd = _initialize_command(cmd)

assert _cmd.description == "(Deprecated) Description"
assert _cmd.hidden is True

def test_deprecated_command_no_description(self):
@deprecated("Do not use for this reason")
@command()
def cmd():
return True

assert hasattr(cmd, "_command")

_cmd = _initialize_command(cmd)

assert _cmd.description == "(Deprecated)"
assert _cmd.hidden is True


class TestShutdown(object):
"""Test shutdown decorator"""

Expand Down
Loading