Skip to content

feat: implement Prompt.save() with automatic dirty tracking for name changes #504

Open
thiagobomfin-galileo wants to merge 2 commits intomainfrom
feat/sc-52119-prompt-save-future
Open

feat: implement Prompt.save() with automatic dirty tracking for name changes #504
thiagobomfin-galileo wants to merge 2 commits intomainfrom
feat/sc-52119-prompt-save-future

Conversation

@thiagobomfin-galileo
Copy link
Contributor

@thiagobomfin-galileo thiagobomfin-galileo commented Mar 13, 2026

User description

Shortcut:
Implement Missing CRUD Operations in future Package
Description:

  • Adds setattr dirty-tracking to Prompt so that assigning prompt.name = "..." on a synced
    object automatically transitions its state to DIRTY
  • Implements the DIRTY/FAILED_SYNC branch of Prompt.save(), replacing a NotImplementedError stub
    by delegating to the existing update() method
  • _update_from_api_response() and update() use object.setattr directly when writing name back
    from an API response, bypassing dirty tracking to avoid false DIRTY transitions during internal
    syncs
  • save() DIRTY branch is a one-liner: ID guard + return self.update(name=self.name) — all API
    logic, error handling, and state transitions are already in update()

Design decision: name only

Prompt has two mutable attributes that map to different API endpoints:

    Attribute: name
    API: PUT /prompt-templates/{id}   
    Semantics: In-place rename

    Attribute: messages
    API: POST /prompt-templates/{id}/versions
    Semantics: Creates a new immutable version

Only name is tracked for dirty state. Changing messages requires an explicit create_version() call — making version creation an intentional action rather than a silent side effect of save().

Tests:

  • Unit Tests Added
  • E2E Test Added (if it's a user-facing feature, or fixing a bug)

Generated description

Below is a concise technical summary of the changes proposed in this PR:
Introduce dirty-state tracking for Prompt name assignments so synced objects automatically go DIRTY and API syncs bypass it. Implement Prompt.save() to delegate DIRTY/FAILED_SYNC persistence to GlobalPromptTemplates.update() while reusing existing state transitions and tests covering failure and ID guards.

Latest Contributors(1)
UserCommitDate
vamaq@users.noreply.gi...fix-Future-package-Pro...February 04, 2026
This pull request is reviewed by Baz. Review like a pro on (Baz).

@thiagobomfin-galileo thiagobomfin-galileo self-assigned this Mar 13, 2026
@thiagobomfin-galileo thiagobomfin-galileo requested a review from a team as a code owner March 13, 2026 16:18
@codecov
Copy link

codecov bot commented Mar 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.20%. Comparing base (459457d) to head (8f14053).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #504      +/-   ##
==========================================
+ Coverage   82.08%   82.20%   +0.12%     
==========================================
  Files          96       96              
  Lines        9297     9288       -9     
==========================================
+ Hits         7631     7635       +4     
+ Misses       1666     1653      -13     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +209 to +214
def __setattr__(self, attr_name: str, value: Any) -> None:
"""Track mutations to name and transition to DIRTY state when synced."""
if attr_name in self._TRACKED_FIELDS and hasattr(self, "_sync_state") and self._sync_state == SyncState.SYNCED:
self._set_state(SyncState.DIRTY)
object.__setattr__(self, attr_name, value)

Copy link
Contributor

Choose a reason for hiding this comment

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

Prompt.setattr flips a synced object's state to DIRTY on any assignment to tracked fields without checking equality, so prompt.name = prompt.name becomes DIRTY and save() (lines 775–788) then calls update(name=self.name), issuing an unnecessary API rename. Can we compare the new value to the current one before flipping the sync state so redundant assignments don't trigger updates?

Finding type: Logical Bugs | Severity: 🟠 Medium


Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents:

In src/galileo/__future__/prompt.py around lines 209-214, the __setattr__ method
unconditionally marks tracked fields DIRTY when synced. Change it to first obtain the
current value via object.__getattribute__(self, attr_name) (or use a unique sentinel
with getattr) and compare it to the new value; only call
self._set_state(SyncState.DIRTY) if the values differ. Ensure this comparison handles
the missing-attribute case safely (treat missing as different) and then delegate to
object.__setattr__ to assign the value.

Comment on lines 301 to 304
"""
self.id = retrieved_prompt.id
self.name = retrieved_prompt.name
object.__setattr__(self, "name", retrieved_prompt.name)

Copy link
Contributor

Choose a reason for hiding this comment

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

What's wrong: Both _update_from_api_response and update() now manually call object.__setattr__(self, "name", …) to bypass the new dirty-tracking override when syncing the name. The same three-line pattern appears twice (lines 301‑304 and again around 506‑511), so any future tracked field or additional sync flow will need the same boilerplate.
Impact: We risk forgetting to bypass the overridden __setattr__ in new code paths, which would incorrectly mark the prompt as DIRTY and block save()/update() operations or cause redundant API calls.
Ask: Can we extract a helper (e.g., _set_synced_name(self, name: str) or a generic _assign_tracked_attr(attr, value)) and call it from both _update_from_api_response and update() so the bypass logic lives in one place and isn’t duplicated?

Finding type: Code Dedup and Conventions | Severity: 🟢 Low


Want Baz to fix this for you? Activate Fixer

Comment on lines +785 to +788
# DIRTY or FAILED_SYNC: name was changed, persist it
if self.id is None:
raise ValueError("Prompt ID is not set. Cannot update a prompt without an ID.")
return self.update(name=self.name)
Copy link
Contributor

Choose a reason for hiding this comment

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

Prompt.save() treats any FAILED_SYNC as a name-only dirty state and unconditionally calls self.update(name=self.name) (e.g. after a failed create_version). This can hide the original failure and leave the prompt incorrectly marked SYNCED even though the failed operation never completed. Can we restrict save() to retry only name-related DIRTY changes or record/distinguish the failure source before invoking self.update(name=...)?

Finding type: Logical Bugs | Severity: 🔴 High


Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents:

In src/galileo/__future__/prompt.py around lines 785 to 788, the save() method currently
treats FAILED_SYNC the same as DIRTY and unconditionally calls update(name=self.name),
which can mask unrelated failures. Change this logic so that only SyncState.DIRTY
triggers the update retry: if self.sync_state == SyncState.DIRTY, validate self.id and
call return self.update(name=self.name); if self.sync_state == SyncState.FAILED_SYNC, do
not call update — instead re-raise or surface the stored error (e.g., raise the saved
error from the failed sync state or raise a ValueError instructing the caller to retry
the original operation), preserving the original failure information and avoiding
marking the prompt as SYNCED incorrectly.

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.

1 participant