Skip to content

Commit e8f6a48

Browse files
author
Saurabh Badenkal
committed
Use primary_id_attribute for updates, return typed empty DF, redact LLM logs by default, fix string concat
- prodev: use primary_id_attribute from tables.create() instead of guessing the ID column name (e.g., TABLE_TASKid) - get(): return empty DataFrame with select columns (not columnless) for consistent downstream access on empty results - LLM log: redact prompts/responses by default (include_prompts=False) to avoid logging sensitive data; metadata (timing, provider) always logged - Fix implicit string concatenation in error messages
1 parent dd8b74e commit e8f6a48

3 files changed

Lines changed: 28 additions & 18 deletions

File tree

examples/advanced/datascience_risk_assessment.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -561,8 +561,13 @@ def _summarize_with_template(flagged_df):
561561
return summaries
562562

563563

564-
def _export_llm_log(llm_complete):
565-
"""Export LLM interaction log (prompts, responses, timing) to a text file."""
564+
def _export_llm_log(llm_complete, include_prompts=False):
565+
"""Export LLM interaction log (timing, provider metadata) to a text file.
566+
567+
By default, prompt and response content is not included to avoid logging
568+
sensitive data (PII, customer data). Set include_prompts=True to include
569+
full content for debugging.
570+
"""
566571
log_path = OUTPUT_DIR / "llm_interactions.txt"
567572
with open(log_path, "w", encoding="utf-8") as f:
568573
f.write("LLM Interaction Log\n")
@@ -576,9 +581,12 @@ def _export_llm_log(llm_complete):
576581

577582
for i, entry in enumerate(llm_complete.log, 1):
578583
f.write(f"--- Call {i} ({entry['elapsed_seconds']:.2f}s) ---\n\n")
579-
f.write(f"[System Prompt]\n{entry['system_prompt']}\n\n")
580-
f.write(f"[User Prompt]\n{entry['user_prompt']}\n\n")
581-
f.write(f"[Response]\n{entry['response']}\n\n")
584+
if include_prompts:
585+
f.write(f"[System Prompt]\n{entry['system_prompt']}\n\n")
586+
f.write(f"[User Prompt]\n{entry['user_prompt']}\n\n")
587+
f.write(f"[Response]\n{entry['response']}\n\n")
588+
else:
589+
f.write(f"[Response length: {len(entry['response'])} chars]\n\n")
582590

583591
print(f"[OK] LLM interaction log saved to {log_path}")
584592

examples/advanced/prodev_quick_start.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def run_demo(client):
106106
print(f"[INFO] Output folder: {OUTPUT_DIR.resolve()}")
107107

108108
# -- Step 1: Create 4 tables --
109-
primary_name_col = step1_create_tables(client)
109+
primary_name_col, primary_id_col = step1_create_tables(client)
110110

111111
# -- Step 2: Create relationships --
112112
step2_create_relationships(client)
@@ -118,7 +118,7 @@ def run_demo(client):
118118
step4_query_and_analyze(client, customer_ids, primary_name_col)
119119

120120
# -- Step 5: Update and delete --
121-
step5_update_and_delete(client, task_ids, primary_name_col)
121+
step5_update_and_delete(client, task_ids, primary_name_col, primary_id_col)
122122

123123
# -- Step 6: Cleanup --
124124
cleanup(client)
@@ -148,10 +148,11 @@ def step1_create_tables(client):
148148
f"{TABLE_CUSTOMER}_Revenue": "money",
149149
},
150150
)
151-
# The primary name column logical name is returned by tables.create()
152-
# so we know exactly what key to use in create payloads.
151+
# The primary column logical names are returned by tables.create()
152+
# so we know exactly what keys to use in payloads and queries.
153153
primary_name_col = result.primary_name_attribute
154-
print(f"[OK] Created table: {TABLE_CUSTOMER} (primary column: {primary_name_col})")
154+
primary_id_col = result.primary_id_attribute
155+
print(f"[OK] Created table: {TABLE_CUSTOMER} (name: {primary_name_col}, id: {primary_id_col})")
155156

156157
# Project table
157158
client.tables.create(
@@ -186,9 +187,9 @@ def step1_create_tables(client):
186187
)
187188
print(f"[OK] Created table: {TABLE_TIMEENTRY}")
188189
print(f"[OK] All 4 tables created (suffix: {SUFFIX})")
189-
print(f"[INFO] Primary name column: '{primary_name_col}'")
190+
print(f"[INFO] Primary name column: '{primary_name_col}', ID column: '{primary_id_col}'")
190191

191-
return primary_name_col
192+
return primary_name_col, primary_id_col
192193

193194

194195
# ================================================================
@@ -451,7 +452,7 @@ def step4_query_and_analyze(client, customer_ids, primary_name_col):
451452
# ================================================================
452453

453454

454-
def step5_update_and_delete(client, task_ids, primary_name_col):
455+
def step5_update_and_delete(client, task_ids, primary_name_col, primary_id_col):
455456
"""Demonstrate update and delete with DataFrames."""
456457
print("\n" + "-" * 60)
457458
print("STEP 5: Update and delete records")
@@ -460,13 +461,14 @@ def step5_update_and_delete(client, task_ids, primary_name_col):
460461
status_col = f"{TABLE_TASK}_Status"
461462

462463
# Update: mark first two tasks as "Complete"
464+
# Use primary_id_col (from tables.create metadata) as the ID column name
463465
update_df = pd.DataFrame(
464466
{
465-
f"{TABLE_TASK}id": [task_ids.iloc[0], task_ids.iloc[1]],
467+
primary_id_col: [task_ids.iloc[0], task_ids.iloc[1]],
466468
status_col: ["Complete", "Complete"],
467469
}
468470
)
469-
client.dataframe.update(TABLE_TASK, update_df, id_column=f"{TABLE_TASK}id")
471+
client.dataframe.update(TABLE_TASK, update_df, id_column=primary_id_col)
470472
print(f"[OK] Updated 2 tasks to 'Complete'")
471473

472474
# Delete: remove the last task

src/PowerPlatform/Dataverse/operations/dataframe.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def get(
142142
rows.extend(row.data for row in batch)
143143

144144
if not rows:
145-
return pd.DataFrame()
145+
return pd.DataFrame(columns=select) if select else pd.DataFrame()
146146
return pd.DataFrame.from_records(rows)
147147

148148
# ----------------------------------------------------------------- create
@@ -287,7 +287,7 @@ def update(
287287
change_columns = [column for column in changes.columns if column != id_column]
288288
if not change_columns:
289289
raise ValueError(
290-
"No columns to update. The DataFrame must contain at least one column " "besides the id_column."
290+
"No columns to update. The DataFrame must contain at least one column besides the id_column."
291291
)
292292
change_list = dataframe_to_records(changes[change_columns], na_as_null=clear_nulls)
293293

@@ -348,7 +348,7 @@ def delete(
348348
invalid = [ids.index[i] for i, v in enumerate(raw_list) if not isinstance(v, str) or not v.strip()]
349349
if invalid:
350350
raise ValueError(
351-
f"ids Series contains invalid values at index(es) {invalid}. " "All IDs must be non-empty strings."
351+
f"ids Series contains invalid values at index(es) {invalid}. " f"All IDs must be non-empty strings."
352352
)
353353
id_list = [v.strip() for v in raw_list]
354354

0 commit comments

Comments
 (0)