Skip to content

Commit d2140e2

Browse files
author
Abel Milash
committed
Add QueryResult.__aiter__ for async for support (ADO 6431066)
QueryResult only defined __iter__, so 'async for r in result' raised TypeError: 'QueryResult' object is not an async iterable. Python's async for does not fall back to __iter__. Added an async __aiter__ that yields the same Record objects, plus 4 unit tests covering presence, full iteration, empty result, and identity with sync iteration.
1 parent 5e27de2 commit d2140e2

3 files changed

Lines changed: 47 additions & 1 deletion

File tree

examples/aio/advanced/walkthrough.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,16 @@ async def _run_walkthrough(client):
265265
record_ids = [r.get("new_walkthroughdemoid")[:8] + "..." for r in page]
266266
print(f" Page {page_num}: {len(page)} records - IDs: {record_ids}")
267267

268+
log_call(
269+
"async for record in await client.query.builder(...).top(5).execute() — QueryResult supports async iteration"
270+
)
271+
print("Iterating a QueryResult with `async for` (top 5 by quantity)...")
272+
top_result = await backoff(
273+
lambda: client.query.builder(table_name).order_by("new_Quantity", descending=True).top(5).execute()
274+
)
275+
async for record in top_result:
276+
print(f" - Qty={record.get('new_quantity')} Title={record.get('new_title')}")
277+
268278
# ============================================================================
269279
# 7. QUERYBUILDER - FLUENT QUERIES
270280
# ============================================================================

src/PowerPlatform/Dataverse/models/record.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from __future__ import annotations
77

88
from dataclasses import dataclass, field
9-
from typing import Any, Dict, Iterator, KeysView, List, Optional, Union, ValuesView, ItemsView
9+
from typing import Any, AsyncIterator, Dict, Iterator, KeysView, List, Optional, Union, ValuesView, ItemsView
1010

1111
__all__ = ["Record", "QueryResult"]
1212

@@ -132,6 +132,10 @@ def __init__(self, records: List[Record]) -> None:
132132
def __iter__(self) -> Iterator[Record]:
133133
return iter(self.records)
134134

135+
async def __aiter__(self) -> AsyncIterator[Record]:
136+
for record in self.records:
137+
yield record
138+
135139
def __len__(self) -> int:
136140
return len(self.records)
137141

tests/unit/aio/test_async_query.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,3 +529,35 @@ def _make_page_resp(page_num: int):
529529
warnings.simplefilter("always")
530530
with pytest.raises(ValidationError, match="exceeded"):
531531
await async_client.query.fetchxml(_SIMPLE_FETCHXML).execute()
532+
533+
534+
class TestQueryResultAsyncIteration:
535+
"""QueryResult must support ``async for`` so callers can write
536+
``async for r in page`` after ``async for page in q.execute_pages()``.
537+
"""
538+
539+
def _records(self, n=3):
540+
return [Record(id=f"id-{i}", table="account", data={"name": f"R{i}"}) for i in range(n)]
541+
542+
async def test_has_aiter(self):
543+
qr = QueryResult(self._records())
544+
assert hasattr(qr, "__aiter__")
545+
546+
async def test_async_for_yields_all_records(self):
547+
qr = QueryResult(self._records(3))
548+
result = [r async for r in qr]
549+
assert len(result) == 3
550+
assert [r["name"] for r in result] == ["R0", "R1", "R2"]
551+
552+
async def test_async_for_empty(self):
553+
qr = QueryResult([])
554+
result = [r async for r in qr]
555+
assert result == []
556+
557+
async def test_sync_and_async_return_same_records(self):
558+
qr = QueryResult(self._records(5))
559+
sync_result = list(qr)
560+
async_result = [r async for r in qr]
561+
assert len(sync_result) == len(async_result)
562+
for s, a in zip(sync_result, async_result):
563+
assert s is a

0 commit comments

Comments
 (0)