diff --git a/stripe/_api_requestor.py b/stripe/_api_requestor.py index 3d245465e..1f70d0616 100644 --- a/stripe/_api_requestor.py +++ b/stripe/_api_requestor.py @@ -78,7 +78,14 @@ def __init__( self._default_proxy = proxy @classmethod + @_util.deprecated( + "This method is internal to stripe-python and the public interface will be removed in a future stripe-python version" + ) def format_app_info(cls, info): + return cls._format_app_info(info) + + @classmethod + def _format_app_info(cls, info): str = info["name"] if info["version"]: str += "/%s" % (info["version"],) @@ -230,7 +237,7 @@ def specific_oauth_error(self, rbody, rcode, resp, rheaders, error_code): def request_headers(self, api_key, method): user_agent = "Stripe/v1 PythonBindings/%s" % (_version.VERSION,) if stripe.app_info: - user_agent += " " + self.format_app_info(stripe.app_info) + user_agent += " " + self._format_app_info(stripe.app_info) ua = { "bindings_version": _version.VERSION, diff --git a/stripe/_api_resource.py b/stripe/_api_resource.py index 8beedc4d5..c16f45d3a 100644 --- a/stripe/_api_resource.py +++ b/stripe/_api_resource.py @@ -8,6 +8,7 @@ ) from stripe._api_requestor import APIRequestor from stripe._stripe_object import StripeObject +from stripe import _util from urllib.parse import quote_plus from typing import ( Any, @@ -28,6 +29,9 @@ class APIResource(StripeObject, Generic[T]): OBJECT_NAME: ClassVar[str] @classmethod + @_util.deprecated( + "This method is deprecated and will be removed in a future version of stripe-python. Child classes of APIResource should define their own `retrieve` and use APIResource._request directly." + ) def retrieve(cls, id, api_key=None, **params) -> T: instance = cls(id, api_key, **params) instance.refresh() diff --git a/stripe/_custom_method.py b/stripe/_custom_method.py index b5f8685d4..b2950f231 100644 --- a/stripe/_custom_method.py +++ b/stripe/_custom_method.py @@ -3,6 +3,10 @@ from urllib.parse import quote_plus +# TODO(major): 1704. +@_util.deprecated( + "the custom_method class decorator will be removed in a future version of stripe-python. Define custom methods directly and use StripeObject._static_request within." +) def custom_method( name: str, http_verb: str, diff --git a/stripe/_error.py b/stripe/_error.py index 54e2434c2..714431147 100644 --- a/stripe/_error.py +++ b/stripe/_error.py @@ -4,6 +4,9 @@ import stripe # noqa: IMP101 from stripe._error_object import ErrorObject +from stripe import _util +import warnings + class StripeError(Exception): _message: Optional[str] @@ -43,7 +46,7 @@ def __init__( self.headers = headers or {} self.code = code self.request_id = self.headers.get("request-id", None) - self.error = self.construct_error_object() + self.error = self._construct_error_object() def __str__(self): msg = self._message or "" @@ -68,6 +71,9 @@ def __repr__(self): self.request_id, ) + @_util.deprecated( + "For internal stripe-python use only. The public interface will be removed in a future version." + ) def construct_error_object(self) -> Optional[ErrorObject]: if ( self.json_body is None @@ -81,6 +87,11 @@ def construct_error_object(self) -> Optional[ErrorObject]: self.json_body["error"], stripe.api_key ) + def _construct_error_object(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + return self.construct_error_object() + class APIError(StripeError): pass diff --git a/stripe/_list_object.py b/stripe/_list_object.py index 4d029b489..98bc5faf0 100644 --- a/stripe/_list_object.py +++ b/stripe/_list_object.py @@ -13,9 +13,11 @@ cast, Mapping, ) +from stripe import _util from stripe._stripe_object import StripeObject from urllib.parse import quote_plus +import warnings T = TypeVar("T", bound=StripeObject) @@ -26,6 +28,25 @@ class ListObject(StripeObject, Generic[T]): has_more: bool url: str + def _list( + self, + api_key: Optional[str] = None, + stripe_version: Optional[str] = None, + stripe_account: Optional[str] = None, + **params: Mapping[str, Any] + ) -> Self: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=DeprecationWarning) + return self.list( # pyright: ignore[reportDeprecated] + api_key=api_key, + stripe_version=stripe_version, + stripe_account=stripe_account, + **params, + ) + + @_util.deprecated( + "This will be removed in a future version of stripe-python. Please call the `list` method on the corresponding resource directly, instead of using `list` from the list object." + ) def list( self, api_key: Optional[str] = None, @@ -50,6 +71,9 @@ def list( ), ) + @_util.deprecated( + "This will be removed in a future version of stripe-python. Please call the `create` method on the corresponding resource directly, instead of using `create` from the list object." + ) def create( self, api_key: Optional[str] = None, @@ -76,6 +100,9 @@ def create( ), ) + @_util.deprecated( + "This will be removed in a future version of stripe-python. Please call the `retrieve` method on the corresponding resource directly, instead of using `retrieve` from the list object." + ) def retrieve( self, id: str, @@ -148,11 +175,27 @@ def auto_paging_iter(self) -> Iterator[T]: break @classmethod + @_util.deprecated( + "This method is for internal stripe-python use only. The public interface will be removed in a future version." + ) def empty_list( cls, api_key: Optional[str] = None, stripe_version: Optional[str] = None, stripe_account: Optional[str] = None, + ) -> Self: + return cls._empty_list( + api_key=api_key, + stripe_version=stripe_version, + stripe_account=stripe_account, + ) + + @classmethod + def _empty_list( + cls, + api_key: Optional[str] = None, + stripe_version: Optional[str] = None, + stripe_account: Optional[str] = None, ) -> Self: return cls.construct_from( {"data": []}, @@ -174,7 +217,7 @@ def next_page( **params: Mapping[str, Any] ) -> Self: if not self.has_more: - return self.empty_list( + return self._empty_list( api_key=api_key, stripe_version=stripe_version, stripe_account=stripe_account, @@ -190,13 +233,12 @@ def next_page( params_with_filters.update({"starting_after": last_id}) params_with_filters.update(params) - result = self.list( + return self._list( api_key=api_key, stripe_version=stripe_version, stripe_account=stripe_account, **params_with_filters, ) - return result def previous_page( self, @@ -206,7 +248,7 @@ def previous_page( **params: Mapping[str, Any] ) -> Self: if not self.has_more: - return self.empty_list( + return self._empty_list( api_key=api_key, stripe_version=stripe_version, stripe_account=stripe_account, @@ -222,7 +264,7 @@ def previous_page( params_with_filters.update({"ending_before": first_id}) params_with_filters.update(params) - result = self.list( + result = self._list( api_key=api_key, stripe_version=stripe_version, stripe_account=stripe_account, diff --git a/stripe/_listable_api_resource.py b/stripe/_listable_api_resource.py index b422ad922..9f27facd7 100644 --- a/stripe/_listable_api_resource.py +++ b/stripe/_listable_api_resource.py @@ -5,6 +5,9 @@ T = TypeVar("T", bound=StripeObject) +# TODO(major): 1704 - remove this class and all internal usages. `.list` is already inlined into the resource classes. +# Although we should inline .auto_paging_iter into the resource classes as well. + class ListableAPIResource(APIResource[T]): @classmethod diff --git a/stripe/_nested_resource_class_methods.py b/stripe/_nested_resource_class_methods.py index e0fd1d1c1..54f418a66 100644 --- a/stripe/_nested_resource_class_methods.py +++ b/stripe/_nested_resource_class_methods.py @@ -4,8 +4,8 @@ from stripe._api_resource import APIResource -# TODO(major): Remove this. It is no longer used except for "nested_resource_url" and "nested_resource_request", -# which are unnecessary ande deprecated. +# TODO(major): 1704. Remove this. It is no longer used except for "nested_resource_url" and "nested_resource_request", +# which are unnecessary and deprecated and should also be removed. def nested_resource_class_methods( resource: str, path: Optional[str] = None, diff --git a/stripe/_search_result_object.py b/stripe/_search_result_object.py index c82dd4695..43e0a8e7c 100644 --- a/stripe/_search_result_object.py +++ b/stripe/_search_result_object.py @@ -12,6 +12,8 @@ ) from stripe._stripe_object import StripeObject +from stripe import _util +import warnings T = TypeVar("T", bound=StripeObject) @@ -22,6 +24,25 @@ class SearchResultObject(StripeObject, Generic[T]): has_more: bool next_page: str + def _search( + self, + api_key: Optional[str] = None, + stripe_version: Optional[str] = None, + stripe_account: Optional[str] = None, + **params: Mapping[str, Any] + ) -> Self: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + return self.search( # pyright: ignore[reportDeprecated] + api_key=api_key, + stripe_version=stripe_version, + stripe_account=stripe_account, + **params, + ) + + @_util.deprecated( + "This will be removed in a future version of stripe-python. Please call the `search` method on the corresponding resource directly, instead of the generic search on SearchResultObject." + ) def search( self, api_key: Optional[str] = None, @@ -78,7 +99,7 @@ def auto_paging_iter(self) -> Iterator[T]: break @classmethod - def empty_search_result( + def _empty_search_result( cls, api_key: Optional[str] = None, stripe_version: Optional[str] = None, @@ -92,6 +113,22 @@ def empty_search_result( last_response=None, ) + @classmethod + @_util.deprecated( + "For internal stripe-python use only. This will be removed in a future version." + ) + def empty_search_result( + cls, + api_key: Optional[str] = None, + stripe_version: Optional[str] = None, + stripe_account: Optional[str] = None, + ) -> Self: + return cls._empty_search_result( + api_key=api_key, + stripe_version=stripe_version, + stripe_account=stripe_account, + ) + @property def is_empty(self) -> bool: return not self.data @@ -104,7 +141,7 @@ def next_search_result_page( **params: Mapping[str, Any] ) -> Self: if not self.has_more: - return self.empty_search_result( + return self._empty_search_result( api_key=api_key, stripe_version=stripe_version, stripe_account=stripe_account, @@ -114,10 +151,9 @@ def next_search_result_page( params_with_filters.update({"page": self.next_page}) params_with_filters.update(params) - result = self.search( + return self._search( api_key=api_key, stripe_version=stripe_version, stripe_account=stripe_account, **params_with_filters, ) - return result diff --git a/stripe/_singleton_api_resource.py b/stripe/_singleton_api_resource.py index 9898a449c..b64b9b3a2 100644 --- a/stripe/_singleton_api_resource.py +++ b/stripe/_singleton_api_resource.py @@ -5,6 +5,8 @@ T = TypeVar("T", bound=StripeObject) +# TODO(major): 1704 - Inline into Tax.Settings and Balance, and remove this class. + class SingletonAPIResource(APIResource[T]): @classmethod diff --git a/stripe/_stripe_object.py b/stripe/_stripe_object.py index f45559c95..b375cf619 100644 --- a/stripe/_stripe_object.py +++ b/stripe/_stripe_object.py @@ -23,6 +23,7 @@ from stripe import _util from stripe._stripe_response import StripeResponse, StripeStreamResponse +import warnings @overload @@ -70,13 +71,19 @@ def _serialize_list( class StripeObject(Dict[str, Any]): - class ReprJSONEncoder(json.JSONEncoder): + class _ReprJSONEncoder(json.JSONEncoder): def default(self, o: Any) -> Any: if isinstance(o, datetime.datetime): # pyright complains that _encode_datetime is "private", but it's # private to outsiders, not to stripe_object return _encode_datetime(o) - return super(StripeObject.ReprJSONEncoder, self).default(o) + return super(StripeObject._ReprJSONEncoder, self).default(o) + + @_util.deprecated( + "For internal stripe-python use only. The public interface will be removed in a future version" + ) + class ReprJSONEncoder(_ReprJSONEncoder): + pass _retrieve_params: Dict[str, Any] _previous: Optional[Dict[str, Any]] @@ -307,6 +314,9 @@ def refresh_from( self._previous = values @classmethod + @_util.deprecated( + "This will be removed in a future version of stripe-python." + ) def api_base(cls) -> Optional[str]: return None @@ -351,10 +361,14 @@ def _request( stripe_version = stripe_version or self.stripe_version api_key = api_key or self.api_key params = params or self._retrieve_params + api_base = None + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + api_base = self.api_base() # pyright: ignore[reportDeprecated] requestor = stripe.APIRequestor( key=api_key, - api_base=self.api_base(), + api_base=api_base, api_version=stripe_version, account=stripe_account, ) @@ -378,9 +392,13 @@ def request_stream( ) -> StripeStreamResponse: if params is None: params = self._retrieve_params + api_base = None + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + api_base = self.api_base() # pyright: ignore[reportDeprecated] requestor = stripe.APIRequestor( key=self.api_key, - api_base=self.api_base(), + api_base=api_base, api_version=self.stripe_version, account=self.stripe_account, ) @@ -407,23 +425,26 @@ def __repr__(self) -> str: def __str__(self) -> str: return json.dumps( - self.to_dict_recursive(), + self._to_dict_recursive(), sort_keys=True, indent=2, - cls=self.ReprJSONEncoder, + cls=self._ReprJSONEncoder, ) + @_util.deprecated( + "Deprecated. The public interface will be removed in a future version." + ) def to_dict(self) -> Dict[str, Any]: return dict(self) - def to_dict_recursive(self) -> Dict[str, Any]: + def _to_dict_recursive(self) -> Dict[str, Any]: def maybe_to_dict_recursive( value: Optional[Union[StripeObject, Dict[str, Any]]] ) -> Optional[Dict[str, Any]]: if value is None: return None elif isinstance(value, StripeObject): - return value.to_dict_recursive() + return value._to_dict_recursive() else: return value @@ -434,7 +455,16 @@ def maybe_to_dict_recursive( for key, value in dict(self).items() } + @_util.deprecated( + "For internal stripe-python use only. The public interface will be removed in a future version." + ) + def to_dict_recursive(self) -> Dict[str, Any]: + return self._to_dict_recursive() + @property + @_util.deprecated( + "For internal stripe-python use only. The public interface will be removed in a future version." + ) def stripe_id(self) -> Optional[str]: return getattr(self, "id") diff --git a/stripe/_util.py b/stripe/_util.py index 527df1224..3b2bde7a8 100644 --- a/stripe/_util.py +++ b/stripe/_util.py @@ -25,16 +25,18 @@ # Used for global variables import stripe # noqa: IMP101 -from stripe._stripe_response import StripeResponse -from stripe._stripe_object import StripeObject + +if TYPE_CHECKING: + from stripe._stripe_response import StripeResponse + from stripe._stripe_object import StripeObject STRIPE_LOG = os.environ.get("STRIPE_LOG") logger: logging.Logger = logging.getLogger("stripe") -if hasattr(typing_extensions, "deprecated"): +if TYPE_CHECKING: deprecated = typing_extensions.deprecated -elif not TYPE_CHECKING: +else: _T = TypeVar("_T") # Copied from python/typing_extensions, as this was added in typing_extensions 4.5.0 which is incompatible with @@ -192,12 +194,12 @@ def get_object_classes(): return OBJECT_CLASSES -Resp = Union[StripeResponse, Dict[str, Any], List["Resp"]] +Resp = Union["StripeResponse", Dict[str, Any], List["Resp"]] @overload def convert_to_stripe_object( - resp: Union[StripeResponse, Dict[str, Any]], + resp: Union["StripeResponse", Dict[str, Any]], api_key: Optional[str] = None, stripe_version: Optional[str] = None, stripe_account: Optional[str] = None, @@ -232,6 +234,10 @@ def convert_to_stripe_object( # the raw API response information stripe_response = None + # Imports here at runtime to avoid circular dependencies + from stripe._stripe_response import StripeResponse + from stripe._stripe_object import StripeObject + if isinstance(resp, StripeResponse): stripe_response = resp resp = cast(Resp, stripe_response.data) diff --git a/tests/test_api_requestor.py b/tests/test_api_requestor.py index 4133e3da4..803a65067 100644 --- a/tests/test_api_requestor.py +++ b/tests/test_api_requestor.py @@ -11,7 +11,7 @@ from stripe._stripe_response import StripeResponse, StripeStreamResponse from stripe._encode import _api_encode -from urllib.parse import urlsplit +from urllib.parse import urlsplit, parse_qsl import urllib3 @@ -135,7 +135,7 @@ def __init__(self, expected): def __eq__(self, other): query = urlsplit(other).query or other - parsed = stripe.util.parse_qsl(query) + parsed = parse_qsl(query) return self.expected == sorted(parsed) def __repr__(self): @@ -158,7 +158,7 @@ def __eq__(self, other): ) return False - q_matcher = QueryMatcher(stripe.util.parse_qsl(self.exp_parts.query)) + q_matcher = QueryMatcher(parse_qsl(self.exp_parts.query)) return q_matcher == other def __repr__(self): @@ -447,7 +447,7 @@ def test_methods_with_params_and_response( if method == "post": check_call( method, - post_data=QueryMatcher(stripe.util.parse_qsl(encoded)), + post_data=QueryMatcher(parse_qsl(encoded)), ) else: abs_url = "%s%s?%s" % ( @@ -487,7 +487,7 @@ def test_methods_with_params_and_streaming_response( if method == "post": check_call( method, - post_data=QueryMatcher(stripe.util.parse_qsl(encoded)), + post_data=QueryMatcher(parse_qsl(encoded)), is_streaming=True, ) else: diff --git a/tests/test_exports.py b/tests/test_exports.py index f25b84a5a..9c7d9b980 100644 --- a/tests/test_exports.py +++ b/tests/test_exports.py @@ -180,7 +180,7 @@ def test_can_import_abstract() -> None: from stripe import SearchableAPIResource as SearchableFromStripe from stripe import VerifyMixin as VerifyMixinFromStripe from stripe import APIResourceTestHelpers as APIResourceTestHelpersFromStripe - from stripe import custom_method as custom_methodFromStripe + from stripe import custom_method as custom_methodFromStripe # pyright: ignore[reportDeprecated] from stripe import nested_resource_class_methods as nested_resource_class_methodsFromStripe # fmt: on