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

Add @util.deprecated decorator #1096

Merged
merged 4 commits into from
Oct 25, 2023
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 flake8_stripe/flake8_stripe.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class TypingImportsChecker:
"Iterator",
"Mapping",
"Set",
"Callable",
]

def __init__(self, tree: ast.AST):
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
zip_safe=False,
install_requires=[
'typing_extensions <= 4.2.0, > 3.7.2; python_version < "3.7"',
'typing_extensions >= 4.0.0; python_version >= "3.7"',
# The best typing support comes from 4.5.0+ but we can support down to
# 3.7.2 without throwing exceptions.
'typing_extensions >= 4.5.0; python_version >= "3.7"',
'requests >= 2.20; python_version >= "3.0"',
],
python_requires=">=3.6",
Expand Down
8 changes: 3 additions & 5 deletions stripe/api_resources/abstract/updateable_api_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@ def modify(cls, sid, **params) -> T:
url = "%s/%s" % (cls.class_url(), quote_plus(sid))
return cast(T, cls._static_request("post", url, params=params))

@util.deprecated(
"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."
)
def save(self, idempotency_key=None):
"""
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.
"""
updated_params = self.serialize(None)
if updated_params:
self._request_and_refresh(
Expand Down
63 changes: 63 additions & 0 deletions stripe/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import sys
import os
import re
import warnings

import stripe
from urllib.parse import parse_qsl, quote_plus

from typing_extensions import Type, TYPE_CHECKING
from typing import (
Callable,
TypeVar,
Union,
overload,
Expand All @@ -22,6 +24,7 @@
)

from stripe.stripe_response import StripeResponse
import typing_extensions

if TYPE_CHECKING:
from stripe.stripe_object import StripeObject
Expand All @@ -37,8 +40,68 @@
"log_debug",
"dashboard_link",
"logfmt",
"deprecated",
]

if hasattr(typing_extensions, "deprecated"):
deprecated = typing_extensions.deprecated
elif not TYPE_CHECKING:
_T = TypeVar("_T")

# Copied from python/typing_extensions, as this was added in typing_extensions 4.5.0 which is incompatible with
# python 3.6. We still need `deprecated = typing_extensions.deprecated` in addition to this fallback, as
# IDEs (pylance) specially detect references to symbols defined in `typing_extensions`
#
# https://github.com/python/typing_extensions/blob/5d20e9eed31de88667542ba5a6f66e6dc439b681/src/typing_extensions.py#L2289-L2370
def deprecated(
__msg: str,
*,
category: Optional[Type[Warning]] = DeprecationWarning,
stacklevel: int = 1,
) -> Callable[[_T], _T]:
def decorator(__arg: _T) -> _T:
if category is None:
__arg.__deprecated__ = __msg
return __arg
elif isinstance(__arg, type):
original_new = __arg.__new__
has_init = __arg.__init__ is not object.__init__

@functools.wraps(original_new)
def __new__(cls, *args, **kwargs):
warnings.warn(
__msg, category=category, stacklevel=stacklevel + 1
)
if original_new is not object.__new__:
return original_new(cls, *args, **kwargs)
# Mirrors a similar check in object.__new__.
elif not has_init and (args or kwargs):
raise TypeError(f"{cls.__name__}() takes no arguments")
else:
return original_new(cls)

__arg.__new__ = staticmethod(__new__)
__arg.__deprecated__ = __new__.__deprecated__ = __msg
return __arg
elif callable(__arg):

@functools.wraps(__arg)
def wrapper(*args, **kwargs):
warnings.warn(
__msg, category=category, stacklevel=stacklevel + 1
)
return __arg(*args, **kwargs)

__arg.__deprecated__ = wrapper.__deprecated__ = __msg
return wrapper
else:
raise TypeError(
"@deprecated decorator with non-None category must be applied to "
f"a class or callable, not {__arg!r}"
)

return decorator


def is_appengine_dev():
return "APPENGINE_RUNTIME" in os.environ and "Dev" in os.environ.get(
Expand Down