Skip to content

Commit 5729153

Browse files
Add @util.deprecated decorator (#1096)
1 parent 7d0a73f commit 5729153

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
@@ -14,12 +14,10 @@ def modify(cls, sid, **params) -> T:
1414
url = "%s/%s" % (cls.class_url(), quote_plus(sid))
1515
return cast(T, cls._static_request("post", url, params=params))
1616

17+
@util.deprecated(
18+
"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."
19+
)
1720
def save(self, idempotency_key=None):
18-
"""
19-
The `save` method is deprecated and will be removed in a future major version of the library.
20-
21-
Use the class method `modify` on the resource instead.
22-
"""
2321
updated_params = self.serialize(None)
2422
if updated_params:
2523
self._request_and_refresh(

stripe/util.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
import sys
77
import os
88
import re
9+
import warnings
910

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

1314
from typing_extensions import Type, TYPE_CHECKING
1415
from typing import (
16+
Callable,
1517
TypeVar,
1618
Union,
1719
overload,
@@ -23,6 +25,7 @@
2325
)
2426

2527
from stripe.stripe_response import StripeResponse
28+
import typing_extensions
2629

2730
if TYPE_CHECKING:
2831
from stripe.stripe_object import StripeObject
@@ -38,8 +41,68 @@
3841
"log_debug",
3942
"dashboard_link",
4043
"logfmt",
44+
"deprecated",
4145
]
4246

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

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

0 commit comments

Comments
 (0)