Skip to content

save() silently no-ops on property unlink after from_id re-fetch (stale client cache) #110

@apdavison

Description

@apdavison

Summary

After modifying an object and saving it, re-fetching the same object via from_id(use_cache=True) (the default) returns the original cached document, not the post-save state. As a result, the next save() can be a silent no-op even when a modification was made locally — most visibly when trying to unlink subjects/specimens by setting a property to None. [Bug reported by Lyuba Zehl]

Minimal example

from fairgraph import KGClient
from fairgraph.openminds.core import DatasetVersion, Subject

client = KGClient(host="core.kg.ebrains.eu")

dsv_uuid = "..."  # an existing DatasetVersion you own
sub_uri = "..."   # any existing Subject URI

# 1. Load, link a subject, save.
dsv = DatasetVersion.from_id(dsv_uuid, client, scope="any")
dsv.studied_specimens = [Subject(id=sub_uri)]
dsv.save(client, recursive=False)
# KG now has studiedSpecimen pointing at sub_uri. ✓

# 2. Re-fetch via from_id (same client, default use_cache=True).
dsv = DatasetVersion.from_id(dsv_uuid, client, scope="any")

# 3. Try to unlink the subject and save.
dsv.studied_specimens = None
dsv.save(client, recursive=False)

# Expected: studiedSpecimen cleared in KG.
# Actual:   subject is still linked. No PATCH was sent.

Running the same studied_specimens = None; save() on the original dsv from step 1 (without the intervening re-fetch) does correctly unlink — that path works.

Cause

KGClient.cache[uri] is populated on first fetch in instance_from_full_uri (client.py) and never invalidated or refreshed by writes. When save() calls update_instance / replace_instance / create_new_instance, the server is updated but client.cache[uri] retains the original document.

On the second from_id call:

  • instance_from_full_uri returns the stale cached document.
  • The freshly-constructed DatasetVersion therefore has studied_specimens = None and remote_data with no studiedSpecimen key.
  • Setting studied_specimens = None is a no-op (already None).
  • modified_data() compares current=None against remote=None → empty diff.
  • save() enters the "unchanged, not updating" branch and never sends a PATCH.

The user-facing symptom is the same for any other property that was added by an earlier save in the same session: the second save will silently drop the change because the local view of "what the server has" is wrong.

Workaround until fixed: pass use_cache=False to from_id (or client.cache.clear()) between saves.

Related

Part of the broader caching review tracked in #80.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions