From fd2313e7d453d8e2be2309b97e542d13ed21be81 Mon Sep 17 00:00:00 2001 From: jar-stripe Date: Mon, 10 Aug 2026 12:16:19 -0700 Subject: [PATCH 1/2] Add async iteration to v2 list pagination (#1874) Committed-By-Agent: goose --- stripe/v2/_list_object.py | 29 ++++- tests/api_resources/test_list_object_v2.py | 126 +++++++++++++++++++++ 2 files changed, 153 insertions(+), 2 deletions(-) diff --git a/stripe/v2/_list_object.py b/stripe/v2/_list_object.py index 8e301edce..08fcbd016 100644 --- a/stripe/v2/_list_object.py +++ b/stripe/v2/_list_object.py @@ -1,5 +1,6 @@ +from stripe._any_iterator import AnyIterator from stripe._stripe_object import StripeObject -from typing import List, Optional, TypeVar, Generic +from typing import AsyncIterator, Iterator, List, Optional, TypeVar, Generic T = TypeVar("T", bound=StripeObject) @@ -40,7 +41,13 @@ def __len__(self): def __reversed__(self): return getattr(self, "data", []).__reversed__() - def auto_paging_iter(self): + def auto_paging_iter(self) -> AnyIterator[T]: + return AnyIterator( + self._auto_paging_iter(), + self._auto_paging_iter_async(), + ) + + def _auto_paging_iter(self) -> Iterator[T]: page = self.data next_page_url = self.next_page_url while True: @@ -57,3 +64,21 @@ def auto_paging_iter(self): assert isinstance(result, ListObject) page = result.data next_page_url = result.next_page_url + + async def _auto_paging_iter_async(self) -> AsyncIterator[T]: + page = self.data + next_page_url = self.next_page_url + while True: + for item in page: + yield item + if next_page_url is None: + break + + result = await self._request_async( + "get", + next_page_url, + base_address="api", + ) + assert isinstance(result, ListObject) + page = result.data + next_page_url = result.next_page_url diff --git a/tests/api_resources/test_list_object_v2.py b/tests/api_resources/test_list_object_v2.py index 7767b6a36..84759e873 100644 --- a/tests/api_resources/test_list_object_v2.py +++ b/tests/api_resources/test_list_object_v2.py @@ -146,3 +146,129 @@ def test_iter_forwards_api_key(self, http_client_mock: HTTPClientMock): query_string=query_string_2, api_key="sk_test_iter_forwards_options", ) + + +class TestAutoPagingAsync: + @staticmethod + def pageable_model_response(ids, next_page_url): + return { + "data": [{"id": id, "object": "pageablemodel"} for id in ids], + "next_page_url": next_page_url, + } + + @pytest.mark.anyio + async def test_iter_one_page(self, http_client_mock): + lo = ListObject.construct_from( + self.pageable_model_response(["pm_123", "pm_124"], None), "mykey" + ) + + http_client_mock.assert_no_request() + + seen = [item["id"] async for item in lo.auto_paging_iter()] + + assert seen == ["pm_123", "pm_124"] + + @pytest.mark.anyio + async def test_iter_two_pages(self, http_client_mock): + method = "get" + path = "/v2/pageablemodels" + + lo = ListObject.construct_from( + self.pageable_model_response( + ["pm_123", "pm_124"], + "/v2/pageablemodels?foo=bar&page=page_2", + ), + None, + ) + + http_client_mock.stub_request( + method, + path=path, + query_string="foo=bar&page=page_3", + rbody=json.dumps( + self.pageable_model_response(["pm_127", "pm_128"], None) + ), + ) + + http_client_mock.stub_request( + method, + path=path, + query_string="foo=bar&page=page_2", + rbody=json.dumps( + self.pageable_model_response( + ["pm_125", "pm_126"], + "/v2/pageablemodels?foo=bar&page=page_3", + ) + ), + ) + + seen = [item["id"] async for item in lo.auto_paging_iter()] + + http_client_mock.assert_requested( + method, path=path, query_string="foo=bar&page=page_2" + ) + http_client_mock.assert_requested( + method, path=path, query_string="foo=bar&page=page_3" + ) + + assert seen == [ + "pm_123", + "pm_124", + "pm_125", + "pm_126", + "pm_127", + "pm_128", + ] + + @pytest.mark.anyio + async def test_iter_forwards_api_key( + self, http_client_mock: HTTPClientMock + ): + client = stripe.StripeClient( + http_client=http_client_mock.get_mock_http_client(), + api_key="sk_test_xyz", + ) + + method = "get" + query_string_1 = "object_id=obj_123" + query_string_2 = "object_id=obj_123&page=page_2" + path = "/v2/core/events" + + http_client_mock.stub_request( + method, + path=path, + query_string=query_string_1, + rbody='{"data": [{"id": "x"}], "next_page_url": "/v2/core/events?object_id=obj_123&page=page_2"}', + rcode=200, + rheaders={}, + ) + + http_client_mock.stub_request( + method, + path=path, + query_string=query_string_2, + rbody='{"data": [{"id": "y"}, {"id": "z"}], "next_page_url": null}', + rcode=200, + rheaders={}, + ) + + lo = await client.v2.core.events.list_async( + params={"object_id": "obj_123"}, + options={"api_key": "sk_test_iter_forwards_options"}, + ) + + seen = [item["id"] async for item in lo.auto_paging_iter()] + + assert seen == ["x", "y", "z"] + http_client_mock.assert_requested( + method, + path=path, + query_string=query_string_1, + api_key="sk_test_iter_forwards_options", + ) + http_client_mock.assert_requested( + method, + path=path, + query_string=query_string_2, + api_key="sk_test_iter_forwards_options", + ) From 3f562ca72ce8c00e668fac2d8f184420565cb02e Mon Sep 17 00:00:00 2001 From: David Brownman Date: Mon, 10 Aug 2026 15:08:16 -0700 Subject: [PATCH 2/2] Bump version to 15.5.0 --- CHANGELOG.md | 16 ++++++++++++++++ VERSION | 2 +- pyproject.toml | 2 +- stripe/_version.py | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f25c1887..c0dd72c4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 15.5.0 - 2026-08-10 +* [#1874](https://github.com/stripe/stripe-python/pull/1874) Add async iteration to v2 list auto-pagination + - Adds `async for` support to v2 `ListObject.auto_paging_iter()`. +* [#1869](https://github.com/stripe/stripe-python/pull/1869) Surface `object` property on `EventNotification` +* [#1867](https://github.com/stripe/stripe-python/pull/1867) Emit Claude Code plugin hint at module load time + - Emits new Claude Code plugin hint when `CLAUDECODE` or `CLAUDE_CODE_CHILD_SESSION` environment variables are detected. +* [#1855](https://github.com/stripe/stripe-python/pull/1855) add/adjust event parsing helpers + + - Added methods that return their respective `Event`/`EventNotification` class instances without verifying authenticity. Use them when you've previously verified an event (e.g. you verified, put the event in a queue, and are now processing). Supports events from [AWS EventBridge](https://docs.stripe.com/event-destinations/eventbridge) and [Azure Event Grid](https://docs.stripe.com/event-destinations/eventgrid) natively. + - `Webhook.construct_event_without_verification(payload)` + - `StripeClient.construct_event_without_verification(payload)` + - `StripeClient.parse_event_notification_without_verification(payload)` + - Added `WebhookSignature.generate_signature_header(payload, secret, timestamp=None)`, which computes a full `Stripe-Signature` header for the given payload. Useful for unit tests! + +* [#1863](https://github.com/stripe/stripe-python/pull/1863) Add `stripe.major_api_version` constant + ## 15.4.0 - 2026-07-29 This release changes the pinned API version to 2026-07-29.dahlia. diff --git a/VERSION b/VERSION index c915b5db7..188dd74f5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -15.4.0 +15.5.0 diff --git a/pyproject.toml b/pyproject.toml index 6fb9df204..7f508ea08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "stripe" -version = "15.4.0" +version = "15.5.0" readme = "README.md" description = "Python bindings for the Stripe API" authors = [{ name = "Stripe", email = "support@stripe.com" }] diff --git a/stripe/_version.py b/stripe/_version.py index ea5978f38..bb5abc68a 100644 --- a/stripe/_version.py +++ b/stripe/_version.py @@ -1 +1 @@ -VERSION = "15.4.0" +VERSION = "15.5.0"