Skip to content

Bug bash fixes: Add QueryResult.__aiter__ for async (ADO 6431066)#187

Open
abelmilash-msft wants to merge 1 commit into
mainfrom
users/abelmilash/bugbash_fixes
Open

Bug bash fixes: Add QueryResult.__aiter__ for async (ADO 6431066)#187
abelmilash-msft wants to merge 1 commit into
mainfrom
users/abelmilash/bugbash_fixes

Conversation

@abelmilash-msft
Copy link
Copy Markdown
Contributor

Summary

Addresses 6 bugs from the async/auth bug bash.

Bug Area Fix
6431970 Query Enforce client-side SELECT TOP N limit when server pagination returns more rows
6431066 Async models QueryResult.__aiter__ is now an actual async generator
6431085 Auth (sync + async) Cache AccessToken, refresh ~5 min before expiry with jitter, coalesce concurrent acquisitions under a lock
6431086 Auth (sync + async) Wrap credential get_token exceptions in AuthenticationError so bearer tokens / PII don't leak via raw provider strings
6431942 Records records.create() raises ValueError for empty data instead of POSTing an empty body
6431966 Records _validate_guid rejects non-UUID record_id / ids before the HTTP call, with a clear error

Changes

Core fixes

  • data/_odata_base.py_SQL_TOP_RE regex + _extract_top_limit(sql) -> int | None helper, shared by sync and async OData clients.
  • data/_odata.py / aio/data/_async_odata.py_query_sql extracts the TOP limit once, then truncates value at 4 points (bare-list response, after first page, mid-pagination, final return) and drops @odata.nextLink when the cap is met. Stops paginating early.
  • core/_auth.py / aio/core/_async_auth.py_AuthManager now holds the AccessToken plus a threading.Lock / asyncio.Lock. Uses double-checked locking to coalesce concurrent token acquisitions. Refreshes at expires_on − 300s with up to 60s random jitter. Wraps any credential exception in AuthenticationError(...) from exc so the original is preserved in __cause__ but the bearer token / PII never appears in str(exc).
  • core/errors.py — New AuthenticationError(DataverseError) exported in __all__.
  • models/record.pyQueryResult.__aiter__ rewritten as async def ... yield record so async for record in result: iterates over an actual AsyncIterator[Record] rather than awaiting a coroutine.
  • operations/records.py / aio/operations/async_records.py — Module-level _validate_guid(record_id, param_name="record_id") helper called from retrieve, update, delete (single + list forms). Catches (ValueError, AttributeError, TypeError) so None, int, malformed strings, and missing attrs all raise ValueError("... is not a valid GUID") before any HTTP request. create() raises on empty data.

Tests

  • tests/unit/aio/core/test_async_auth.py — New: cache reuse, near-expiry refresh, expired-token refresh, 20-task concurrent coalescing, exception sanitization (sentinel token string never appears in str(exc); __cause__ preserved).
  • tests/unit/core/test_auth.py — Sync counterparts.
  • tests/unit/data/test_sql_guardrails.py — TOP truncation across the 4 truncation points.
  • tests/unit/test_records_operations.pyNone and int GUID rejection cases.
  • Misc test updates across sync/async records, query, and dataframe suites.

Tests: 2452 passed on the merged branch.

Backwards compatibility

  • No public API removed or renamed.
  • New AuthenticationError is added to core.errors.__all__; it subclasses DataverseError, so existing except DataverseError handlers keep working.
  • _validate_guid rejection raises ValueError for inputs that previously produced an opaque HTTP 400/404 — strictly a clearer-error-earlier change, not a behavior reversal.

Copilot AI review requested due to automatic review settings May 28, 2026 19:34
@abelmilash-msft abelmilash-msft requested a review from a team as a code owner May 28, 2026 19:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Addresses six bug-bash issues spanning query truncation, async iteration, auth token caching/sanitisation, and input validation in the records API.

Changes:

  • Client-side SELECT TOP N enforcement in _query_sql (sync + async), backed by a new _extract_top_limit helper and _SQL_TOP_RE regex on _ODataBase.
  • Auth refactor: _AuthManager / _AsyncAuthManager cache AccessToken with double-checked locking and proactive refresh, and wrap credential errors in a new AuthenticationError so token strings can't leak via raw exception text.
  • records.create() rejects empty data; retrieve/update/delete validate GUIDs via a shared _validate_guid helper; QueryResult.__aiter__ is now a real async generator.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/PowerPlatform/Dataverse/data/_odata_base.py Adds _SQL_TOP_RE and _extract_top_limit shared helper.
src/PowerPlatform/Dataverse/data/_odata.py Truncates _query_sql results to TOP and skips further pagination.
src/PowerPlatform/Dataverse/aio/data/_async_odata.py Async counterpart of TOP truncation.
src/PowerPlatform/Dataverse/core/_auth.py Adds AccessToken caching, lock-coalesced refresh, sanitized AuthenticationError wrapping.
src/PowerPlatform/Dataverse/aio/core/_async_auth.py Async counterpart of caching/sanitisation.
src/PowerPlatform/Dataverse/core/errors.py Adds AuthenticationError(DataverseError) and exports it.
src/PowerPlatform/Dataverse/models/record.py Rewrites QueryResult.__aiter__ as an async generator.
src/PowerPlatform/Dataverse/operations/records.py Adds _validate_guid, empty-data check in create, GUID checks in CRUD.
src/PowerPlatform/Dataverse/aio/operations/async_records.py Async counterpart of GUID validation and empty-data check.
tests/unit/core/test_auth.py New cache, near-expiry, and sanitisation tests for sync auth.
tests/unit/aio/core/test_async_auth.py Async counterparts including a 20-task coalescing test.
tests/unit/data/test_sql_guardrails.py TOP parsing and truncation enforcement tests.
tests/unit/test_records_operations.py Empty-payload and bad-GUID rejection cases; updates fake IDs to valid GUIDs.
tests/unit/aio/test_async_records.py Async records tests updated to use valid GUIDs.
tests/unit/aio/test_async_query.py New async iteration tests for QueryResult.
tests/unit/test_phase3_ga.py Updates fake IDs to a valid _GUID constant.
tests/unit/test_client_dataframe.py Updates fake IDs to valid GUIDs.
tests/unit/test_dataframe_operations.py Updates fake IDs to valid GUIDs.
tests/unit/aio/test_async_dataframe.py Updates fake IDs to valid GUIDs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +95 to +109
if not self._need_new_token():
return _TokenPair(resource=scope, access_token=self._token.token)
with self._lock:
# Double-check after acquiring lock — another thread may have refreshed.
if not self._need_new_token():
return _TokenPair(resource=scope, access_token=self._token.token)
try:
self._token = self.credential.get_token(scope)
except Exception as exc:
raise AuthenticationError(
f"Token acquisition failed for scope '{scope}'. "
f"Check credential configuration. ({type(exc).__name__})",
) from exc
self._refresh_jitter = random.randint(0, _MAX_REFRESH_JITTER_SECONDS)
return _TokenPair(resource=scope, access_token=self._token.token)
Comment on lines +34 to +41
try:
uuid.UUID(record_id)
except (ValueError, AttributeError, TypeError):
raise ValueError(
f"{param_name} {record_id!r} is not a valid GUID "
f"(expected 8-4-4-4-12 hex, e.g. 'a1b2c3d4-1234-5678-9abc-def012345678'). "
f"Pass a uuid.UUID(...) or a string returned by records.create()."
)
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.
@abelmilash-msft abelmilash-msft force-pushed the users/abelmilash/bugbash_fixes branch from 10488b5 to fda8de7 Compare May 28, 2026 20:31
@abelmilash-msft abelmilash-msft changed the title Bug bash fixes: SQL TOP enforcement, async iteration, auth caching/sanitization, GUID & empty-payload validation Bug bash fixes: async iteration May 28, 2026
@abelmilash-msft abelmilash-msft changed the title Bug bash fixes: async iteration Bug bash fixes: Add QueryResult.__aiter__ for async for support (ADO 6431066) May 28, 2026
@abelmilash-msft abelmilash-msft changed the title Bug bash fixes: Add QueryResult.__aiter__ for async for support (ADO 6431066) Bug bash fixes: Add QueryResult.__aiter__ for async (ADO 6431066) May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants