From 3487d68bdab6f20e2ab931c8283f63c94862cf31 Mon Sep 17 00:00:00 2001 From: Peter Lamut Date: Tue, 8 Jun 2021 10:30:29 +0200 Subject: [PATCH 1/2] feat: add iterator capability to paged iterators (#200) * feat: add iterator capability to *Iterator classes The *Iterator classes are only _iterables_, and this commit also makes them _iterators_, i.e. calling next(iterator) on them now works. * Make AsyncIterator an actual async iterator --- google/api_core/page_iterator.py | 10 +++++++++ google/api_core/page_iterator_async.py | 7 ++++++ tests/asyncio/test_page_iterator_async.py | 27 +++++++++++++++++++++++ tests/unit/test_page_iterator.py | 20 +++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/google/api_core/page_iterator.py b/google/api_core/page_iterator.py index fff3b556..49879bc9 100644 --- a/google/api_core/page_iterator.py +++ b/google/api_core/page_iterator.py @@ -170,6 +170,8 @@ def __init__( max_results=None, ): self._started = False + self.__active_iterator = None + self.client = client """Optional[Any]: The client that created this iterator.""" self.item_to_value = item_to_value @@ -228,6 +230,14 @@ def __iter__(self): self._started = True return self._items_iter() + def __next__(self): + if self.__active_iterator is None: + self.__active_iterator = iter(self) + return next(self.__active_iterator) + + # Preserve Python 2 compatibility. + next = __next__ + def _page_iter(self, increment): """Generator of pages of API responses. diff --git a/google/api_core/page_iterator_async.py b/google/api_core/page_iterator_async.py index a0aa41a7..c0725758 100644 --- a/google/api_core/page_iterator_async.py +++ b/google/api_core/page_iterator_async.py @@ -101,6 +101,8 @@ def __init__( max_results=None, ): self._started = False + self.__active_aiterator = None + self.client = client """Optional[Any]: The client that created this iterator.""" self.item_to_value = item_to_value @@ -159,6 +161,11 @@ def __aiter__(self): self._started = True return self._items_aiter() + async def __anext__(self): + if self.__active_aiterator is None: + self.__active_aiterator = self.__aiter__() + return await self.__active_aiterator.__anext__() + async def _page_aiter(self, increment): """Generator of pages of API responses. diff --git a/tests/asyncio/test_page_iterator_async.py b/tests/asyncio/test_page_iterator_async.py index 42fac2a2..4abacc6a 100644 --- a/tests/asyncio/test_page_iterator_async.py +++ b/tests/asyncio/test_page_iterator_async.py @@ -47,6 +47,33 @@ def test_constructor(self): assert iterator.next_page_token == token assert iterator.num_results == 0 + @pytest.mark.asyncio + async def test_anext(self): + parent = mock.sentinel.parent + page_1 = page_iterator_async.Page( + parent, ("item 1.1", "item 1.2"), page_iterator_async._item_to_value_identity + ) + page_2 = page_iterator_async.Page( + parent, ("item 2.1",), page_iterator_async._item_to_value_identity + ) + + async_iterator = PageAsyncIteratorImpl(None, None) + async_iterator._next_page = mock.AsyncMock(side_effect=[page_1, page_2, None]) + + # Consume items and check the state of the async_iterator. + assert async_iterator.num_results == 0 + assert await async_iterator.__anext__() == "item 1.1" + assert async_iterator.num_results == 1 + + assert await async_iterator.__anext__() == "item 1.2" + assert async_iterator.num_results == 2 + + assert await async_iterator.__anext__() == "item 2.1" + assert async_iterator.num_results == 3 + + with pytest.raises(StopAsyncIteration): + await async_iterator.__anext__() + def test_pages_property_starts(self): iterator = PageAsyncIteratorImpl(None, None) diff --git a/tests/unit/test_page_iterator.py b/tests/unit/test_page_iterator.py index 83595376..97b0657b 100644 --- a/tests/unit/test_page_iterator.py +++ b/tests/unit/test_page_iterator.py @@ -109,6 +109,26 @@ def test_constructor(self): assert iterator.next_page_token == token assert iterator.num_results == 0 + def test_next(self): + iterator = PageIteratorImpl(None, None) + page_1 = page_iterator.Page( + iterator, ("item 1.1", "item 1.2"), page_iterator._item_to_value_identity + ) + page_2 = page_iterator.Page( + iterator, ("item 2.1",), page_iterator._item_to_value_identity + ) + iterator._next_page = mock.Mock(side_effect=[page_1, page_2, None]) + + result = next(iterator) + assert result == "item 1.1" + result = next(iterator) + assert result == "item 1.2" + result = next(iterator) + assert result == "item 2.1" + + with pytest.raises(StopIteration): + next(iterator) + def test_pages_property_starts(self): iterator = PageIteratorImpl(None, None) From 2768346433d8934f722c24b1671634d9617e968a Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 8 Jun 2021 08:53:49 -0600 Subject: [PATCH 2/2] chore: release 1.30.0 (#201) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ google/api_core/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6373aae7..d4e292bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-api-core/#history +## [1.30.0](https://www.github.com/googleapis/python-api-core/compare/v1.29.0...v1.30.0) (2021-06-08) + + +### Features + +* add iterator capability to paged iterators ([#200](https://www.github.com/googleapis/python-api-core/issues/200)) ([3487d68](https://www.github.com/googleapis/python-api-core/commit/3487d68bdab6f20e2ab931c8283f63c94862cf31)) + ## [1.29.0](https://www.github.com/googleapis/python-api-core/compare/v1.28.0...v1.29.0) (2021-06-02) diff --git a/google/api_core/version.py b/google/api_core/version.py index e5c10108..ed763cfa 100644 --- a/google/api_core/version.py +++ b/google/api_core/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.29.0" +__version__ = "1.30.0"