From c2c99951bd31cc1c395297ae56f15b3d3c0fb822 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Fri, 10 Feb 2023 11:49:23 -0800 Subject: [PATCH 1/7] chore: Add decorator @deprecated and @pending_deprecation --- samtranslator/internal/__init__.py | 6 ++ samtranslator/internal/deprecation_control.py | 65 +++++++++++++++++++ samtranslator/model/types.py | 2 + tests/internal/test_deprecation_control.py | 39 +++++++++++ 4 files changed, 112 insertions(+) create mode 100644 samtranslator/internal/__init__.py create mode 100644 samtranslator/internal/deprecation_control.py create mode 100644 tests/internal/test_deprecation_control.py diff --git a/samtranslator/internal/__init__.py b/samtranslator/internal/__init__.py new file mode 100644 index 000000000..c1f1239df --- /dev/null +++ b/samtranslator/internal/__init__.py @@ -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. +""" diff --git a/samtranslator/internal/deprecation_control.py b/samtranslator/internal/deprecation_control.py new file mode 100644 index 000000000..d966a3b10 --- /dev/null +++ b/samtranslator/internal/deprecation_control.py @@ -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." + + +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]]: + """ + 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 diff --git a/samtranslator/model/types.py b/samtranslator/model/types.py index 917f5c4bb..f9e4cb1e0 100644 --- a/samtranslator/model/types.py +++ b/samtranslator/model/types.py @@ -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 @@ -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 diff --git a/tests/internal/test_deprecation_control.py b/tests/internal/test_deprecation_control.py new file mode 100644 index 000000000..38d523b65 --- /dev/null +++ b/tests/internal/test_deprecation_control.py @@ -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), + ) From 12038a16c2f5f41565966be6d96a59d8420ab0d2 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Fri, 10 Feb 2023 12:15:48 -0800 Subject: [PATCH 2/7] Update message --- samtranslator/internal/deprecation_control.py | 4 +++- tests/internal/test_deprecation_control.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/samtranslator/internal/deprecation_control.py b/samtranslator/internal/deprecation_control.py index d966a3b10..a3f239379 100644 --- a/samtranslator/internal/deprecation_control.py +++ b/samtranslator/internal/deprecation_control.py @@ -33,7 +33,9 @@ def deprecated(replacement: Optional[str]) -> Callable[[Callable[..., RT]], Call 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) + warning_message = _make_message( + f"{func.__name__} is deprecated and will be removed in a future release", 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) diff --git a/tests/internal/test_deprecation_control.py b/tests/internal/test_deprecation_control.py index 38d523b65..147f8fd6e 100644 --- a/tests/internal/test_deprecation_control.py +++ b/tests/internal/test_deprecation_control.py @@ -25,7 +25,9 @@ def test_deprecated_decorator(self): 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) + "deprecated_function is deprecated and will be removed in a future release, " + "please consider to use replacement_function", + str(w[-1].message), ) def test_pending_deprecation_decorator(self): From 21bec50a6d9632e429f575e132992903587da764 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Fri, 10 Feb 2023 12:25:09 -0800 Subject: [PATCH 3/7] Remove @pending_deprecation --- samtranslator/internal/deprecation_control.py | 23 +------------------ tests/internal/test_deprecation_control.py | 15 ------------ 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/samtranslator/internal/deprecation_control.py b/samtranslator/internal/deprecation_control.py index a3f239379..8f9e16638 100644 --- a/samtranslator/internal/deprecation_control.py +++ b/samtranslator/internal/deprecation_control.py @@ -3,7 +3,7 @@ The warning message is written to stderr when shown. For the difference between DeprecationWarning -and PendingDeprecationWarning, refer to +and other deprecation warning classes, refer to https://peps.python.org/pep-0565/#additional-use-case-for-futurewarning If external packages import deprecated/pending-deprecation @@ -44,24 +44,3 @@ def wrapper(*args, **kwargs) -> RT: # type: ignore return wrapper return decorator - - -def pending_deprecation(replacement: Optional[str]) -> Callable[[Callable[..., RT]], Callable[..., RT]]: - """ - 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 diff --git a/tests/internal/test_deprecation_control.py b/tests/internal/test_deprecation_control.py index 147f8fd6e..860a39a4a 100644 --- a/tests/internal/test_deprecation_control.py +++ b/tests/internal/test_deprecation_control.py @@ -13,11 +13,6 @@ 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: @@ -29,13 +24,3 @@ def test_deprecated_decorator(self): "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), - ) From 8ffb81b4e833d2ad47fa9ffaf4cfb0355c6a79d9 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Fri, 10 Feb 2023 12:26:27 -0800 Subject: [PATCH 4/7] Update message and format code --- samtranslator/internal/deprecation_control.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/samtranslator/internal/deprecation_control.py b/samtranslator/internal/deprecation_control.py index 8f9e16638..249e38508 100644 --- a/samtranslator/internal/deprecation_control.py +++ b/samtranslator/internal/deprecation_control.py @@ -17,9 +17,7 @@ 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." + return f"{message}, please use {replacement}" if replacement else f"{message} and there is no replacement." def deprecated(replacement: Optional[str]) -> Callable[[Callable[..., RT]], Callable[..., RT]]: From 31d8ee2b37364606f67580278411d76daa2d241c Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Fri, 10 Feb 2023 12:27:20 -0800 Subject: [PATCH 5/7] Update module docstring --- samtranslator/internal/deprecation_control.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samtranslator/internal/deprecation_control.py b/samtranslator/internal/deprecation_control.py index 249e38508..87d199c79 100644 --- a/samtranslator/internal/deprecation_control.py +++ b/samtranslator/internal/deprecation_control.py @@ -6,8 +6,8 @@ and other deprecation warning classes, 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. +If external packages import deprecated interfaces, +it is their responsibility to detect and remove them. """ import warnings from functools import wraps From 741f28a74c30f2a05ec9521f7c045744f445d870 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Fri, 10 Feb 2023 12:29:15 -0800 Subject: [PATCH 6/7] Remove "there is no replacement --- samtranslator/internal/deprecation_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samtranslator/internal/deprecation_control.py b/samtranslator/internal/deprecation_control.py index 87d199c79..808408878 100644 --- a/samtranslator/internal/deprecation_control.py +++ b/samtranslator/internal/deprecation_control.py @@ -17,7 +17,7 @@ def _make_message(message: str, replacement: Optional[str]) -> str: - return f"{message}, please use {replacement}" if replacement else f"{message} and there is no replacement." + return f"{message}, please use {replacement}" if replacement else message def deprecated(replacement: Optional[str]) -> Callable[[Callable[..., RT]], Callable[..., RT]]: From e2642760d4ccf4e34817b7ac776831b71f46d593 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Fri, 10 Feb 2023 12:30:20 -0800 Subject: [PATCH 7/7] Fix unit test --- tests/internal/test_deprecation_control.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/internal/test_deprecation_control.py b/tests/internal/test_deprecation_control.py index 860a39a4a..8edd00869 100644 --- a/tests/internal/test_deprecation_control.py +++ b/tests/internal/test_deprecation_control.py @@ -1,7 +1,7 @@ import warnings from unittest import TestCase -from samtranslator.internal.deprecation_control import deprecated, pending_deprecation +from samtranslator.internal.deprecation_control import deprecated def replacement_function(x, y): @@ -21,6 +21,6 @@ def test_deprecated_decorator(self): self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) self.assertIn( "deprecated_function is deprecated and will be removed in a future release, " - "please consider to use replacement_function", + "please use replacement_function", str(w[-1].message), )