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