Skip to content

Fill in empty properties when an object is recognized through the save cache - #135

Open
apdavison wants to merge 1 commit into
HumanBrainProject:masterfrom
apdavison:fix-exists-cache-empty-properties
Open

Fill in empty properties when an object is recognized through the save cache#135
apdavison wants to merge 1 commit into
HumanBrainProject:masterfrom
apdavison:fix-exists-cache-empty-properties

Conversation

@apdavison

Copy link
Copy Markdown
Member

Fixes #134.

KGObject.exists() has three ways of finding an object that already exists in the KG, but only two of them fill in the properties that were left empty locally. The branch that recognizes an object from the save cache took the cached object's remote_data without doing so.

That breaks the invariant save() depends on: _update_empty_properties() distinguishes "never set" from "deliberately set to None" by diffing against remote_data, so populating remote_data while leaving a property empty makes that property look like a deliberate deletion. modified_data() then reports it as changed, and save() sends it to the KG as null.

The trigger is simply saving the same object twice in one session from separately-constructed objects. Metadata-harvesting scripts do this routinely — they build a fresh Person for each role someone holds and each project they appear in — and were silently erasing people's contactInformation, affiliation, alternateName and digitalIdentifier on the second save.

The fix calls _update_empty_properties() in that branch too, so all three paths through exists() behave the same way. Assigning remote_data directly also left the two objects sharing a single dict, as the # copy or update needed? comment there suspected; the back-fill gives each its own equal but independent copy.

Tests in test/test_base.py cover the back-fill, the fact that values set locally still win and are still written, and a save that must send nothing; test/test_openminds_core.py has a regression test for the person-saved-twice case. All run offline against the mock client, which can now serve seeded instances, answer existence queries against them, and record what gets written.

…e cache

exists() has three ways of finding an object that already exists in the KG, but only two of them filled in the properties that were left empty locally. The save cache branch took the cached object's remote_data without doing so, which made every property that is set in the KG but absent from the object look like a deliberate deletion to modified_data(), so the next call to save() set it to null. Metadata-harvesting scripts, which typically build a fresh object for each role a person holds, were losing people's contact information, affiliations and ORCIDs the second time a person was saved in a single run.

Assigning remote_data directly also left the two objects sharing a single dict, as the comment there suspected; the back-fill gives each its own.

The mock client used in the tests can now serve seeded instances, answer existence queries against them, and record what gets written, so a save can be followed end to end.
@apdavison apdavison added this to the 0.15 milestone Aug 31, 2026
@apdavison apdavison added the bug Something isn't working label Aug 31, 2026
@apdavison apdavison moved this from Todo to In Progress in fairgraph development Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

save() sets properties to null when the same object is saved twice in one session

1 participant