8383from PowerPlatform .Dataverse .models .record import QueryResult
8484from PowerPlatform .Dataverse .models .table_info import TableInfo
8585
86-
8786# ---------------------------------------------------------------------------
8887# Shared helpers
8988# ---------------------------------------------------------------------------
9089
9190SEPARATOR = "=" * 72
9291_WARN_GAP_MS = 200.0 # max acceptable canary gap in milliseconds
93- _WARN_SPEEDUP = 2.0 # min acceptable speedup ratio
92+ _WARN_SPEEDUP = 2.0 # min acceptable speedup ratio
9493
9594
9695def heading (title : str ) -> None :
@@ -113,6 +112,7 @@ def _speedup_line(label: str, seq_s: float, conc_s: float) -> str:
113112# Canary infrastructure
114113# ---------------------------------------------------------------------------
115114
115+
116116async def _canary (ticks : List [float ], stop : asyncio .Event ) -> None :
117117 """Append monotonic timestamps every 10 ms until *stop* is set."""
118118 while not stop .is_set ():
@@ -146,17 +146,15 @@ async def _with_canary(coro_fn: Callable) -> tuple[Any, float, int, float]:
146146def _canary_line (label : str , elapsed_ms : float , ticks : int , gap_ms : float ) -> tuple [str , bool ]:
147147 ok = gap_ms < _WARN_GAP_MS
148148 status = "[OK] " if ok else "[WARN]"
149- line = (
150- f" { status } { label } \n "
151- f" call={ elapsed_ms :.0f} ms canary_ticks={ ticks } max_gap={ gap_ms :.1f} ms"
152- )
149+ line = f" { status } { label } \n " f" call={ elapsed_ms :.0f} ms canary_ticks={ ticks } max_gap={ gap_ms :.1f} ms"
153150 return line , ok
154151
155152
156153# ---------------------------------------------------------------------------
157154# Test 1: Non-blocking reads (GET operations)
158155# ---------------------------------------------------------------------------
159156
157+
160158async def run_test1_nonblocking_reads (client : AsyncDataverseClient ) -> None :
161159 """
162160 Verify that read operations (GET) do not block the event loop.
@@ -180,21 +178,21 @@ async def run_test1_nonblocking_reads(client: AsyncDataverseClient) -> None:
180178 """
181179
182180 calls = [
183- ("records.list(account, top=5)" ,
184- lambda : client . records . list ( "account" , top = 5 )),
185- ( "tables.list(filter=IsPrivate...)" ,
186- lambda : client .tables .list (
187- filter = "IsPrivate eq false" ,
188- select = ["LogicalName" , "SchemaName" ],
189- ) ),
190- ( "tables.get(account)" ,
191- lambda : client .tables .get ("account" )),
192- ("query.sql(SELECT TOP 5 ...)" ,
193- lambda : client .query .sql ( "SELECT TOP 5 name FROM account ORDER BY name" )),
194- ("query.fetchxml(...).execute()" ,
195- lambda : client . query .fetchxml ( fetchxml ). execute ()) ,
196- ( " query.builder(account). top(5).execute()" ,
197- lambda : client . query . builder ( "account" ). select ( "name" ). top ( 5 ). execute () ),
181+ ("records.list(account, top=5)" , lambda : client . records . list ( "account" , top = 5 )),
182+ (
183+ "tables.list(filter=IsPrivate...)" ,
184+ lambda : client .tables .list (
185+ filter = "IsPrivate eq false" ,
186+ select = ["LogicalName" , "SchemaName" ],
187+ ),
188+ ) ,
189+ ( "tables.get(account)" , lambda : client .tables .get ("account" )),
190+ ("query.sql(SELECT TOP 5 ...)" , lambda : client . query . sql ( "SELECT TOP 5 name FROM account ORDER BY name" )),
191+ ( "query.fetchxml(...).execute()" , lambda : client .query .fetchxml ( fetchxml ). execute ( )),
192+ (
193+ " query.builder(account).top(5). execute()" ,
194+ lambda : client . query .builder (" account" ). select ( "name" ). top (5 ).execute (),
195+ ),
198196 ]
199197
200198 all_ok = True
@@ -219,6 +217,7 @@ async def run_test1_nonblocking_reads(client: AsyncDataverseClient) -> None:
219217# Test 2: Read throughput — sequential vs concurrent
220218# ---------------------------------------------------------------------------
221219
220+
222221async def run_test2_read_throughput (client : AsyncDataverseClient , n : int ) -> None :
223222 """
224223 Compare sequential vs concurrent execution for read operations.
@@ -233,12 +232,9 @@ async def run_test2_read_throughput(client: AsyncDataverseClient, n: int) -> Non
233232 )
234233
235234 ops = [
236- ("records.list(account, top=5)" ,
237- lambda : client .records .list ("account" , top = 5 )),
238- ("query.sql(SELECT TOP 5 ...)" ,
239- lambda : client .query .sql ("SELECT TOP 5 name FROM account ORDER BY name" )),
240- ("tables.get(account)" ,
241- lambda : client .tables .get ("account" )),
235+ ("records.list(account, top=5)" , lambda : client .records .list ("account" , top = 5 )),
236+ ("query.sql(SELECT TOP 5 ...)" , lambda : client .query .sql ("SELECT TOP 5 name FROM account ORDER BY name" )),
237+ ("tables.get(account)" , lambda : client .tables .get ("account" )),
242238 ]
243239
244240 overall_seq = overall_conc = 0.0
@@ -272,6 +268,7 @@ async def run_test2_read_throughput(client: AsyncDataverseClient, n: int) -> Non
272268# Test 3: Write concurrency — POST path
273269# ---------------------------------------------------------------------------
274270
271+
275272async def run_test3_write_concurrency (client : AsyncDataverseClient , n : int ) -> None :
276273 """
277274 Verify that write operations (POST) also benefit from concurrency.
@@ -307,9 +304,7 @@ def _payload(i: int, suffix: str) -> dict:
307304
308305 # Concurrent creates
309306 t0 = time .monotonic ()
310- conc_ids = list (await asyncio .gather (
311- * [client .records .create ("contact" , _payload (i , "Con" )) for i in range (n )]
312- ))
307+ conc_ids = list (await asyncio .gather (* [client .records .create ("contact" , _payload (i , "Con" )) for i in range (n )]))
313308 conc_s = time .monotonic () - t0
314309
315310 line , speedup = _speedup_line (f"records.create(contact) x{ n } " , seq_s , conc_s )
@@ -332,6 +327,7 @@ def _payload(i: int, suffix: str) -> dict:
332327# Test 4: Pagination non-blocking
333328# ---------------------------------------------------------------------------
334329
330+
335331async def run_test4_pagination_nonblocking (client : AsyncDataverseClient ) -> None :
336332 """
337333 Verify that async generators (list_pages, execute_pages) yield between
@@ -374,20 +370,15 @@ async def _paginate_fetchxml():
374370
375371 async def _paginate_builder ():
376372 pages = 0
377- async for _page in (
378- client .query .builder ("account" )
379- .select ("name" )
380- .page_size (5 )
381- .execute_pages ()
382- ):
373+ async for _page in client .query .builder ("account" ).select ("name" ).page_size (5 ).execute_pages ():
383374 pages += 1
384375 if pages >= 3 :
385376 break
386377
387378 paginators = [
388379 ("records.list_pages(account, page_size=5)" , _paginate_records ),
389- ("query.fetchxml(...).execute_pages()" , _paginate_fetchxml ),
390- ("query.builder(...).execute_pages()" , _paginate_builder ),
380+ ("query.fetchxml(...).execute_pages()" , _paginate_fetchxml ),
381+ ("query.builder(...).execute_pages()" , _paginate_builder ),
391382 ]
392383
393384 all_ok = True
@@ -409,6 +400,7 @@ async def _paginate_builder():
409400# Test 5: Mixed fan-out (different operation types simultaneously)
410401# ---------------------------------------------------------------------------
411402
403+
412404async def run_test5_mixed_fanout (client : AsyncDataverseClient ) -> None :
413405 """
414406 Fire different operation types concurrently in a single gather().
@@ -434,13 +426,11 @@ async def run_test5_mixed_fanout(client: AsyncDataverseClient) -> None:
434426 """
435427
436428 ops = {
437- "records.list(account, top=3)" : lambda : client .records .list ("account" , top = 3 ),
438- "tables.get(account)" : lambda : client .tables .get ("account" ),
439- "tables.get(contact)" : lambda : client .tables .get ("contact" ),
440- "query.sql(SELECT TOP 3 ...)" : lambda : client .query .sql (
441- "SELECT TOP 3 name FROM account ORDER BY name"
442- ),
443- "query.fetchxml(...).execute()" : lambda : client .query .fetchxml (fetchxml ).execute (),
429+ "records.list(account, top=3)" : lambda : client .records .list ("account" , top = 3 ),
430+ "tables.get(account)" : lambda : client .tables .get ("account" ),
431+ "tables.get(contact)" : lambda : client .tables .get ("contact" ),
432+ "query.sql(SELECT TOP 3 ...)" : lambda : client .query .sql ("SELECT TOP 3 name FROM account ORDER BY name" ),
433+ "query.fetchxml(...).execute()" : lambda : client .query .fetchxml (fetchxml ).execute (),
444434 "query.builder(account).top(3).execute()" : lambda : (
445435 client .query .builder ("account" ).select ("name" ).top (3 ).execute ()
446436 ),
@@ -486,6 +476,7 @@ async def run_test5_mixed_fanout(client: AsyncDataverseClient) -> None:
486476# Test 6: Error resilience in gather()
487477# ---------------------------------------------------------------------------
488478
479+
489480async def run_test6_error_resilience (client : AsyncDataverseClient ) -> None :
490481 """
491482 Verify that one failing call in asyncio.gather() does not prevent
@@ -507,11 +498,11 @@ async def run_test6_error_resilience(client: AsyncDataverseClient) -> None:
507498 nonexistent_table = "new_TableThatDefinitelyDoesNotExist_xyz987"
508499
509500 coros = [
510- client .records .list ("account" , top = 3 ), # good
511- client .records .list ("contact" , top = 3 ), # good
512- client .query .sql ("SELECT TOP 3 name FROM account ORDER BY name" ), # good
513- client .query .sql (bad_sql ), # bad — invalid SQL
514- client .records .list (nonexistent_table , top = 1 ), # bad — table not found
501+ client .records .list ("account" , top = 3 ), # good
502+ client .records .list ("contact" , top = 3 ), # good
503+ client .query .sql ("SELECT TOP 3 name FROM account ORDER BY name" ), # good
504+ client .query .sql (bad_sql ), # bad — invalid SQL
505+ client .records .list (nonexistent_table , top = 1 ), # bad — table not found
515506 ]
516507
517508 t0 = time .monotonic ()
@@ -543,6 +534,7 @@ async def run_test6_error_resilience(client: AsyncDataverseClient) -> None:
543534# Test 7: Real-world fan-out — metadata for multiple tables
544535# ---------------------------------------------------------------------------
545536
537+
546538async def run_test7_metadata_fanout (client : AsyncDataverseClient ) -> None :
547539 """
548540 Fetch metadata for multiple tables simultaneously.
@@ -589,6 +581,7 @@ async def run_test7_metadata_fanout(client: AsyncDataverseClient) -> None:
589581# Entry point
590582# ---------------------------------------------------------------------------
591583
584+
592585async def main () -> None :
593586 print (SEPARATOR )
594587 print ("Dataverse SDK - Async Concurrency Benchmark & Validation" )
0 commit comments