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

chore: Add decorator @deprecated and add it to is_str() #2889

Merged
merged 7 commits into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions samtranslator/internal/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
The module for samtranslator internal implementations.

External packages should not import anything from it
as all interfaces are subject to change without warning.
"""
65 changes: 65 additions & 0 deletions samtranslator/internal/deprecation_control.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""
Utils for deprecating our code using warning.warn().
The warning message is written to stderr when shown.

For the difference between DeprecationWarning
and PendingDeprecationWarning, refer to
https://peps.python.org/pep-0565/#additional-use-case-for-futurewarning

If external packages import deprecated/pending-deprecation
interfaces, it is their responsibility to detect and remove them.
"""
import warnings
from functools import wraps
from typing import Callable, Optional, TypeVar

RT = TypeVar("RT") # return type


def _make_message(message: str, replacement: Optional[str]) -> str:
if replacement:
return f"{message}, please consider to use {replacement}"
return f"{message} and there is no replacement."
hoffa marked this conversation as resolved.
Show resolved Hide resolved


def deprecated(replacement: Optional[str]) -> Callable[[Callable[..., RT]], Callable[..., RT]]:
"""
Mark a function/method as deprecated.

The warning is shown by default when triggered directly
by code in __main__.
"""

def decorator(func: Callable[..., RT]) -> Callable[..., RT]:
@wraps(func)
def wrapper(*args, **kwargs) -> RT: # type: ignore
warning_message = _make_message(f"{func.__name__} is deprecated", replacement)
# Setting stacklevel=2 to let Python print the line that calls
# this wrapper, not the line below.
warnings.warn(warning_message, DeprecationWarning, stacklevel=2)
return func(*args, **kwargs)

return wrapper

return decorator


def pending_deprecation(replacement: Optional[str]) -> Callable[[Callable[..., RT]], Callable[..., RT]]:
hoffa marked this conversation as resolved.
Show resolved Hide resolved
"""
Mark a function/method as pending deprecation.

The warning is not shown by default in runtime.
"""

def decorator(func: Callable[..., RT]) -> Callable[..., RT]:
@wraps(func)
def wrapper(*args, **kwargs) -> RT: # type: ignore
warning_message = _make_message(f"{func.__name__} will be deprecated", replacement)
# Setting stacklevel=2 to let Python print the line that calls
# this wrapper, not the line below.
warnings.warn(warning_message, PendingDeprecationWarning, stacklevel=2)
return func(*args, **kwargs)

return wrapper

return decorator
2 changes: 2 additions & 0 deletions samtranslator/model/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import Any, Callable, Type, Union

import samtranslator.model.exceptions
from samtranslator.internal.deprecation_control import deprecated

# Validator always looks like def ...(value: Any, should_raise: bool = True) -> bool,
# However, Python type hint doesn't support functions with optional keyword argument
Expand Down Expand Up @@ -137,6 +138,7 @@ def validate(value: Any, should_raise: bool = False) -> bool:
return validate


@deprecated(replacement="IS_STR")
def is_str() -> Validator:
"""
For compatibility reason, we need this `is_str()` as it
Expand Down
39 changes: 39 additions & 0 deletions tests/internal/test_deprecation_control.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import warnings
from unittest import TestCase

from samtranslator.internal.deprecation_control import deprecated, pending_deprecation


def replacement_function(x, y):
return x + y


@deprecated(replacement="replacement_function")
def deprecated_function(x, y):
return x + y


@pending_deprecation(replacement="replacement_function")
def pending_deprecation_function(x, y):
return x + y


class TestDeprecationControl(TestCase):
def test_deprecated_decorator(self):
with warnings.catch_warnings(record=True) as w:
deprecated_function(1, 1)
self.assertEqual(len(w), 1)
self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
self.assertIn(
"deprecated_function is deprecated, please consider to use replacement_function", str(w[-1].message)
)

def test_pending_deprecation_decorator(self):
with warnings.catch_warnings(record=True) as w:
pending_deprecation_function(1, 1)
self.assertEqual(len(w), 1)
self.assertTrue(issubclass(w[-1].category, PendingDeprecationWarning))
self.assertIn(
"pending_deprecation_function will be deprecated, please consider to use replacement_function",
str(w[-1].message),
)