2727
2828import sys
2929import time
30+ from enum import Enum
3031from typing import Optional , Dict , Any
3132from datetime import datetime
3233
@@ -909,7 +910,12 @@ def test_batch_all_operations(client: DataverseClient, table_info: Dict[str, Any
909910 pass
910911
911912
912- def cleanup_test_data (client : DataverseClient , table_info : Dict [str , Any ], record_id : str ) -> None :
913+ def cleanup_test_data (
914+ client : DataverseClient ,
915+ table_info : Dict [str , Any ],
916+ record_id : str ,
917+ picklist_table_schema_name : Optional [str ] = None ,
918+ ) -> None :
913919 """Clean up test data."""
914920 print ("\n -> Cleanup" )
915921 print ("=" * 50 )
@@ -979,6 +985,46 @@ def cleanup_test_data(client: DataverseClient, table_info: Dict[str, Any], recor
979985 else :
980986 print ("Test table kept for future testing" )
981987
988+ # --- Picklist test table cleanup ---
989+ if picklist_table_schema_name :
990+ picklist_cleanup = (
991+ input (f"Do you want to delete the picklist test table '{ picklist_table_schema_name } '? (y/N): " )
992+ .strip ()
993+ .lower ()
994+ )
995+ if picklist_cleanup in ["y" , "yes" ]:
996+ for attempt in range (1 , retries + 1 ):
997+ try :
998+ client .tables .delete (picklist_table_schema_name )
999+ print (f"[OK] Picklist test table '{ picklist_table_schema_name } ' deleted successfully" )
1000+ break
1001+ except HttpError as err :
1002+ status = getattr (err , "status_code" , None )
1003+ if status == 404 :
1004+ if _table_still_exists (client , picklist_table_schema_name ):
1005+ if attempt < retries :
1006+ print (
1007+ f" Picklist table delete retry { attempt } /{ retries } after metadata 404 ({ err } ). Waiting { delay_seconds } s..."
1008+ )
1009+ time .sleep (delay_seconds )
1010+ continue
1011+ print (f"[WARN] Failed to delete picklist test table due to metadata delay: { err } " )
1012+ break
1013+ print ("[OK] Picklist test table deleted successfully (404 reported)." )
1014+ break
1015+ if attempt < retries :
1016+ print (
1017+ f" Picklist table delete retry { attempt } /{ retries } after error ({ err } ). Waiting { delay_seconds } s..."
1018+ )
1019+ time .sleep (delay_seconds )
1020+ continue
1021+ print (f"[WARN] Failed to delete picklist test table: { err } " )
1022+ except Exception as e :
1023+ print (f"[WARN] Failed to delete picklist test table: { e } " )
1024+ break
1025+ else :
1026+ print ("Picklist test table kept for future testing" )
1027+
9821028
9831029def backoff (op , * , delays = (0 , 2 , 5 , 10 , 20 , 20 )):
9841030 """Retry helper with exponential backoff for metadata propagation delays."""
@@ -1260,6 +1306,118 @@ def _get_or_create(schema, columns, label):
12601306 print (f" [WARN] Could not delete { tbl } : { e } " )
12611307
12621308
1309+ def test_picklist_table (client : DataverseClient ) -> str :
1310+ """Create a table with a local picklist column and write/read records.
1311+
1312+ Demonstrates:
1313+ - Defining a local OptionSet via an ``Enum`` subclass passed as the column ``dtype``.
1314+ - Optional multi-language labels via the ``__labels__`` class attribute.
1315+ - Writing records using either the enum member's integer value OR its label.
1316+ - Reading the integer value back, and the formatted label via
1317+ ``include_annotations="OData.Community.Display.V1.FormattedValue"``.
1318+
1319+ Returns the schema name of the table so the caller can clean it up later.
1320+ """
1321+ print ("\n -> Picklist Column Test" )
1322+ print ("=" * 50 )
1323+
1324+ table_schema_name = "test_PicklistAttribute"
1325+
1326+ # Define a local option set as an Enum. Optional __labels__ provides
1327+ # display labels per language code (1033 = English, 1036 = French).
1328+ class TaskStatus (Enum ):
1329+ NotStarted = 1
1330+ InProgress = 2
1331+ Completed = 3
1332+ Cancelled = 4
1333+
1334+ __labels__ = {
1335+ 1033 : {
1336+ "NotStarted" : "Not Started" ,
1337+ "InProgress" : "In Progress" ,
1338+ "Completed" : "Completed" ,
1339+ "Cancelled" : "Cancelled" ,
1340+ },
1341+ 1036 : {
1342+ "NotStarted" : "Non commencé" ,
1343+ "InProgress" : "En cours" ,
1344+ "Completed" : "Terminé" ,
1345+ "Cancelled" : "Annulé" ,
1346+ },
1347+ }
1348+
1349+ record_id : Optional [str ] = None
1350+ try :
1351+ # Drop any leftover table from a prior failed run so this example is idempotent.
1352+ try :
1353+ existing = client .tables .get (table_schema_name )
1354+ if existing :
1355+ print (f" Removing leftover '{ table_schema_name } ' from a previous run..." )
1356+ client .tables .delete (table_schema_name )
1357+ except Exception :
1358+ pass
1359+
1360+ print (f"Creating table '{ table_schema_name } ' with a picklist column 'test_status'..." )
1361+
1362+ client .tables .create (
1363+ table_schema_name ,
1364+ primary_column = "test_name" ,
1365+ columns = {
1366+ "test_status" : TaskStatus , # Enum subclass => local picklist
1367+ "test_notes" : "string" ,
1368+ },
1369+ )
1370+
1371+ table_info = wait_for_table_metadata (client , table_schema_name )
1372+ print (f"[OK] Picklist table ready: entity_set='{ table_info .get ('entity_set_name' )} '" )
1373+
1374+ # --- Insert one record using the enum's integer value ---
1375+ rec_by_int = {
1376+ "test_name" : f"Picklist Int { datetime .now ().strftime ('%H:%M:%S' )} " ,
1377+ "test_status" : TaskStatus .InProgress .value , # integer 2
1378+ "test_notes" : "Created using TaskStatus.InProgress.value" ,
1379+ }
1380+ record_id = client .records .create (table_schema_name , rec_by_int )
1381+ print (f"[OK] Created record by int value: { record_id } (status={ TaskStatus .InProgress .value } )" )
1382+
1383+ # --- Insert another record using the picklist label (SDK resolves label -> int) ---
1384+ rec_by_label = {
1385+ "test_name" : f"Picklist Label { datetime .now ().strftime ('%H:%M:%S' )} " ,
1386+ "test_status" : "Completed" , # resolved via label cache to int 3
1387+ "test_notes" : "Created using label string 'Completed'" ,
1388+ }
1389+ record_id_2 = client .records .create (table_schema_name , rec_by_label )
1390+ print (f"[OK] Created record by label: { record_id_2 } (status='Completed' -> 3)" )
1391+
1392+ # --- Read back including the FormattedValue annotation ---
1393+ annotation = "OData.Community.Display.V1.FormattedValue"
1394+ retrieved = client .records .retrieve (
1395+ table_schema_name ,
1396+ record_id ,
1397+ select = ["test_name" , "test_status" ],
1398+ include_annotations = annotation ,
1399+ )
1400+ status_int = retrieved .get ("test_status" )
1401+ status_label = retrieved .get (f"test_status@{ annotation } " )
1402+ print (f" Retrieved: test_status={ status_int } , formatted='{ status_label } '" )
1403+ assert status_int == TaskStatus .InProgress .value , f"expected { TaskStatus .InProgress .value } , got { status_int } "
1404+
1405+ # --- List records, filtering by the picklist column ---
1406+ completed = client .records .list (
1407+ table_schema_name ,
1408+ select = ["test_name" , "test_status" ],
1409+ filter = f"test_status eq { TaskStatus .Completed .value } " ,
1410+ include_annotations = annotation ,
1411+ )
1412+ print (f"[OK] Query by picklist value found { len (completed )} 'Completed' record(s)." )
1413+
1414+ except HttpError as e :
1415+ print (f"[ERR] HTTP error during picklist test: { e } " )
1416+ raise
1417+
1418+ return table_schema_name
1419+
1420+
12631421def _table_still_exists (client : DataverseClient , table_schema_name : Optional [str ]) -> bool :
12641422 if not table_schema_name :
12651423 return False
@@ -1304,6 +1462,9 @@ def main():
13041462 # Test querying
13051463 test_query_records (client , table_info )
13061464
1465+ # Test picklist (local OptionSet) column creation, write, read
1466+ picklist_table_info = test_picklist_table (client )
1467+
13071468 # Test relationships
13081469 test_relationships (client )
13091470
@@ -1321,13 +1482,14 @@ def main():
13211482 print ("[OK] Record Creation: Success" )
13221483 print ("[OK] Record Reading: Success" )
13231484 print ("[OK] Record Querying: Success" )
1485+ print ("[OK] Picklist Column: Success" )
13241486 print ("[OK] Relationship Operations: Success" )
13251487 print ("[OK] SQL Encoding: Success" )
13261488 print ("[OK] Batch Operations: Success" )
13271489 print ("\n Your PowerPlatform Dataverse Client SDK is fully functional!" )
13281490
13291491 # Cleanup
1330- cleanup_test_data (client , table_info , record_id )
1492+ cleanup_test_data (client , table_info , record_id , picklist_table_info )
13311493
13321494 except KeyboardInterrupt :
13331495 print ("\n \n [WARN] Test interrupted by user" )
0 commit comments