From afb8179adbfd9ef36c88bdd5fab9166b59d81f9a Mon Sep 17 00:00:00 2001 From: David Brownman <109395161+xavdid-stripe@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:04:34 -0800 Subject: [PATCH 1/2] fix typo (#1423) --- stripe/v2/_event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stripe/v2/_event.py b/stripe/v2/_event.py index 693644b4f..215971a09 100644 --- a/stripe/v2/_event.py +++ b/stripe/v2/_event.py @@ -89,11 +89,11 @@ class RelatedObject: def __init__(self, d) -> None: self.id = d["id"] - self.type_ = d["type"] + self.type = d["type"] self.url = d["url"] def __repr__(self) -> str: - return f"" + return f"" class ThinEvent: From f12452819538ae6567fc8bb5dddb84b8d145ef59 Mon Sep 17 00:00:00 2001 From: David Brownman Date: Wed, 6 Aug 2025 11:21:28 -0700 Subject: [PATCH 2/2] add support for v2 deleted objects --- stripe/_api_requestor.py | 6 +++ stripe/_util.py | 11 ++++- stripe/v2/__init__.py | 1 + stripe/v2/_deleted_object.py | 15 ++++++ stripe/v2/core/_event_destination_service.py | 13 +++--- tests/test_api_requestor.py | 49 ++++++++++++++++++++ 6 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 stripe/v2/_deleted_object.py diff --git a/stripe/_api_requestor.py b/stripe/_api_requestor.py index c0fa8a561..3d5964c33 100644 --- a/stripe/_api_requestor.py +++ b/stripe/_api_requestor.py @@ -66,6 +66,10 @@ _default_proxy: Optional[str] = None +def is_v2_delete_resp(method: str, api_mode: ApiMode) -> bool: + return method == "delete" and api_mode == "V2" + + class _APIRequestor(object): _instance: ClassVar["_APIRequestor|None"] = None @@ -201,6 +205,7 @@ def request( params=params, requestor=requestor, api_mode=api_mode, + is_v2_deleted_object=is_v2_delete_resp(method, api_mode), ) return obj @@ -234,6 +239,7 @@ async def request_async( params=params, requestor=requestor, api_mode=api_mode, + is_v2_deleted_object=is_v2_delete_resp(method, api_mode), ) return obj diff --git a/stripe/_util.py b/stripe/_util.py index 2ef97e7c2..f3016b313 100644 --- a/stripe/_util.py +++ b/stripe/_util.py @@ -272,6 +272,7 @@ def _convert_to_stripe_object( klass_: Optional[Type["StripeObject"]] = None, requestor: "_APIRequestor", api_mode: ApiMode, + is_v2_deleted_object: bool = False, ) -> "StripeObject": ... @@ -283,6 +284,7 @@ def _convert_to_stripe_object( klass_: Optional[Type["StripeObject"]] = None, requestor: "_APIRequestor", api_mode: ApiMode, + is_v2_deleted_object: bool = False, ) -> List["StripeObject"]: ... @@ -293,6 +295,8 @@ def _convert_to_stripe_object( klass_: Optional[Type["StripeObject"]] = None, requestor: "_APIRequestor", api_mode: ApiMode, + # if true, we should ignore the `object` field for finding the class name. This is set by the API requestor + is_v2_deleted_object: bool = False, ) -> Union["StripeObject", List["StripeObject"]]: # If we get a StripeResponse, we'll want to return a # StripeObject with the last_response field filled out with @@ -321,7 +325,12 @@ def _convert_to_stripe_object( resp = resp.copy() klass_name = resp.get("object") if isinstance(klass_name, str): - if api_mode == "V2" and klass_name == "v2.core.event": + if is_v2_deleted_object: + # circular import + from stripe.v2._deleted_object import DeletedObject + + klass = DeletedObject + elif api_mode == "V2" and klass_name == "v2.core.event": event_name = resp.get("type", "") klass = get_thin_event_classes().get( event_name, stripe.StripeObject diff --git a/stripe/v2/__init__.py b/stripe/v2/__init__.py index 73418d2cf..d3821e5a6 100644 --- a/stripe/v2/__init__.py +++ b/stripe/v2/__init__.py @@ -6,6 +6,7 @@ from stripe.v2 import billing as billing, core as core from stripe.v2._billing_service import BillingService as BillingService from stripe.v2._core_service import CoreService as CoreService +from stripe.v2._deleted_object import DeletedObject as DeletedObject from stripe.v2._event import Event as Event from stripe.v2._event_destination import EventDestination as EventDestination # The end of the section generated from our OpenAPI spec diff --git a/stripe/v2/_deleted_object.py b/stripe/v2/_deleted_object.py new file mode 100644 index 000000000..d28bd7559 --- /dev/null +++ b/stripe/v2/_deleted_object.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# File generated from our OpenAPI spec +from stripe._stripe_object import StripeObject +from typing import Optional + + +class DeletedObject(StripeObject): + id: str + """ + The ID of the object that's being deleted. + """ + object: Optional[str] + """ + String representing the object's type. Objects of the same type share the same value of the object field. + """ diff --git a/stripe/v2/core/_event_destination_service.py b/stripe/v2/core/_event_destination_service.py index b8e78c65e..848fa448f 100644 --- a/stripe/v2/core/_event_destination_service.py +++ b/stripe/v2/core/_event_destination_service.py @@ -3,10 +3,11 @@ from stripe._request_options import RequestOptions from stripe._stripe_service import StripeService from stripe._util import sanitize_id +from stripe.v2._deleted_object import DeletedObject from stripe.v2._event import Event from stripe.v2._event_destination import EventDestination from stripe.v2._list_object import ListObject -from typing import Dict, List, Optional, cast +from typing import Dict, List, cast from typing_extensions import Literal, NotRequired, TypedDict @@ -124,7 +125,7 @@ class UpdateParams(TypedDict): """ Additional fields to include in the response. Currently supports `webhook_endpoint.url`. """ - metadata: NotRequired[Dict[str, Optional[str]]] + metadata: NotRequired[Dict[str, str]] """ Metadata. """ @@ -226,12 +227,12 @@ def delete( id: str, params: "EventDestinationService.DeleteParams" = {}, options: RequestOptions = {}, - ) -> EventDestination: + ) -> DeletedObject: """ Delete an event destination. """ return cast( - EventDestination, + DeletedObject, self._request( "delete", "/v2/core/event_destinations/{id}".format(id=sanitize_id(id)), @@ -246,12 +247,12 @@ async def delete_async( id: str, params: "EventDestinationService.DeleteParams" = {}, options: RequestOptions = {}, - ) -> EventDestination: + ) -> DeletedObject: """ Delete an event destination. """ return cast( - EventDestination, + DeletedObject, await self._request_async( "delete", "/v2/core/event_destinations/{id}".format(id=sanitize_id(id)), diff --git a/tests/test_api_requestor.py b/tests/test_api_requestor.py index 0c8b028d4..4ee937ca0 100644 --- a/tests/test_api_requestor.py +++ b/tests/test_api_requestor.py @@ -11,6 +11,7 @@ import stripe from stripe import util from stripe._api_requestor import _api_encode, _APIRequestor +from stripe._customer import Customer from stripe._request_options import RequestOptions from stripe._requestor_options import ( RequestorOptions, @@ -21,6 +22,7 @@ StripeStreamResponse, StripeStreamResponseAsync, ) +from stripe.v2._deleted_object import DeletedObject from tests.http_client_mock import HTTPClientMock VALID_API_METHODS = ("get", "post", "delete") @@ -471,6 +473,53 @@ def test_methods_with_params_and_streaming_response( ) http_client_mock.assert_requested(method, abs_url=abs_url) + def test_delete_methods(self, requestor, http_client_mock): + for path in [self.v1_path, self.v2_path]: + method = "delete" + http_client_mock.stub_request( + method, + path=path, + rbody=json.dumps({"id": "abc_123", "object": "customer"}), + rcode=200, + ) + + resp = requestor.request(method, path, {}, base_address="api") + + http_client_mock.assert_requested(method, post_data=None) + + if path == self.v1_path: + assert isinstance(resp, Customer) + else: + assert isinstance(resp, DeletedObject) + + assert resp.id == "abc_123" + assert resp.object == "customer" + + @pytest.mark.anyio + async def test_delete_methods_async(self, requestor, http_client_mock): + for path in [self.v1_path, self.v2_path]: + method = "delete" + http_client_mock.stub_request( + method, + path=path, + rbody=json.dumps({"id": "abc_123", "object": "customer"}), + rcode=200, + ) + + resp = await requestor.request_async( + method, path, {}, base_address="api" + ) + + http_client_mock.assert_requested(method, post_data=None) + + if path == self.v1_path: + assert isinstance(resp, Customer) + else: + assert isinstance(resp, DeletedObject) + + assert resp.id == "abc_123" + assert resp.object == "customer" + def test_uses_headers(self, requestor, http_client_mock): http_client_mock.stub_request( "get", path=self.v1_path, rbody="{}", rcode=200