Skip to content

Commit 455536e

Browse files
Add @util.deprecated decorator (#1096)
1 parent 0c994c2 commit 455536e

File tree

4 files changed

+70
-6
lines changed

4 files changed

+70
-6
lines changed

flake8_stripe/flake8_stripe.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class TypingImportsChecker:
5050
"Iterator",
5151
"Mapping",
5252
"Set",
53+
"Callable",
5354
]
5455

5556
def __init__(self, tree: ast.AST):

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
zip_safe=False,
3333
install_requires=[
3434
'typing_extensions <= 4.2.0, > 3.7.2; python_version < "3.7"',
35-
'typing_extensions >= 4.0.0; python_version >= "3.7"',
35+
# The best typing support comes from 4.5.0+ but we can support down to
36+
# 3.7.2 without throwing exceptions.
37+
'typing_extensions >= 4.5.0; python_version >= "3.7"',
3638
'requests >= 2.20; python_version >= "3.0"',
3739
],
3840
python_requires=">=3.6",

stripe/api_resources/abstract/updateable_api_resource.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@ def modify(cls, sid, **params) -> T:
1313
url = "%s/%s" % (cls.class_url(), quote_plus(sid))
1414
return cast(T, cls._static_request("post", url, params=params))
1515

16+
@util.deprecated(
17+
"The `save` method is deprecated and will be removed in a future major version of the library. Use the class method `modify` on the resource instead."
18+
)
1619
def save(self, idempotency_key=None):
17-
"""
18-
The `save` method is deprecated and will be removed in a future major version of the library.
19-
20-
Use the class method `modify` on the resource instead.
21-
"""
2220
updated_params = self.serialize(None)
2321
if updated_params:
2422
self._request_and_refresh(

stripe/util.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import sys
66
import os
77
import re
8+
import warnings
89

910
import stripe
1011
from urllib.parse import parse_qsl, quote_plus
1112

1213
from typing_extensions import Type, TYPE_CHECKING
1314
from typing import (
15+
Callable,
1416
TypeVar,
1517
Union,
1618
overload,
@@ -22,6 +24,7 @@
2224
)
2325

2426
from stripe.stripe_response import StripeResponse
27+
import typing_extensions
2528

2629
if TYPE_CHECKING:
2730
from stripe.stripe_object import StripeObject
@@ -37,8 +40,68 @@
3740
"log_debug",
3841
"dashboard_link",
3942
"logfmt",
43+
"deprecated",
4044
]
4145

46+
if hasattr(typing_extensions, "deprecated"):
47+
deprecated = typing_extensions.deprecated
48+
elif not TYPE_CHECKING:
49+
_T = TypeVar("_T")
50+
51+
# Copied from python/typing_extensions, as this was added in typing_extensions 4.5.0 which is incompatible with
52+
# python 3.6. We still need `deprecated = typing_extensions.deprecated` in addition to this fallback, as
53+
# IDEs (pylance) specially detect references to symbols defined in `typing_extensions`
54+
#
55+
# https://github.com/python/typing_extensions/blob/5d20e9eed31de88667542ba5a6f66e6dc439b681/src/typing_extensions.py#L2289-L2370
56+
def deprecated(
57+
__msg: str,
58+
*,
59+
category: Optional[Type[Warning]] = DeprecationWarning,
60+
stacklevel: int = 1,
61+
) -> Callable[[_T], _T]:
62+
def decorator(__arg: _T) -> _T:
63+
if category is None:
64+
__arg.__deprecated__ = __msg
65+
return __arg
66+
elif isinstance(__arg, type):
67+
original_new = __arg.__new__
68+
has_init = __arg.__init__ is not object.__init__
69+
70+
@functools.wraps(original_new)
71+
def __new__(cls, *args, **kwargs):
72+
warnings.warn(
73+
__msg, category=category, stacklevel=stacklevel + 1
74+
)
75+
if original_new is not object.__new__:
76+
return original_new(cls, *args, **kwargs)
77+
# Mirrors a similar check in object.__new__.
78+
elif not has_init and (args or kwargs):
79+
raise TypeError(f"{cls.__name__}() takes no arguments")
80+
else:
81+
return original_new(cls)
82+
83+
__arg.__new__ = staticmethod(__new__)
84+
__arg.__deprecated__ = __new__.__deprecated__ = __msg
85+
return __arg
86+
elif callable(__arg):
87+
88+
@functools.wraps(__arg)
89+
def wrapper(*args, **kwargs):
90+
warnings.warn(
91+
__msg, category=category, stacklevel=stacklevel + 1
92+
)
93+
return __arg(*args, **kwargs)
94+
95+
__arg.__deprecated__ = wrapper.__deprecated__ = __msg
96+
return wrapper
97+
else:
98+
raise TypeError(
99+
"@deprecated decorator with non-None category must be applied to "
100+
f"a class or callable, not {__arg!r}"
101+
)
102+
103+
return decorator
104+
42105

43106
def is_appengine_dev():
44107
return "APPENGINE_RUNTIME" in os.environ and "Dev" in os.environ.get(

0 commit comments

Comments
 (0)