Skip to content

Commit 6b90282

Browse files
committed
sdk: Fix CouchDBIdentifiableStore mutation persistence
Previously, `get_identifiable_by_couchdb_id()` called `update_from()` on the cached object when the same id was already in the `WeakValueDictionary`, silently discarding any in-memory mutations. `get_item()` had no cache-hit check and always went to the network. `commit()` was never implemented despite the `_revision_store` infrastructure already existing. The fix mirrors the changes made to `LocalFileIdentifiableStore`: - `get_identifiable_by_couchdb_id()`: return the cached instance on a hit (preserving mutations) without calling `update_from()`; the `_rev` from the GET response is still stored so future `commit()` calls use the correct revision. - `get_item()`: check the cache first and return immediately on a hit to avoid an unnecessary network round-trip. - Add `commit()`: PUT the serialized object with the stored `_rev`, update `_revision_store` with the new revision on success, and surface 409 Conflict as `CouchDBConflictError`.
1 parent 846f805 commit 6b90282

1 file changed

Lines changed: 36 additions & 7 deletions

File tree

sdk/basyx/aas/backend/couchdb.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -160,22 +160,17 @@ def get_identifiable_by_couchdb_id(self, couchdb_id: str) -> model.Identifiable:
160160
raise KeyError("No Identifiable with couchdb-id {} found in CouchDB database".format(couchdb_id)) from e
161161
raise
162162

163-
# Add CouchDB metadata (for later commits) to object
164163
obj = data['data']
165164
if not isinstance(obj, model.Identifiable):
166165
raise CouchDBResponseError("The CouchDB document with id {} does not contain an identifiable AAS object."
167166
.format(couchdb_id))
168167
set_couchdb_revision("{}/{}/{}".format(self.url, self.database_name, urllib.parse.quote(couchdb_id, safe='')),
169168
data["_rev"])
170169

171-
# If we still have a local replication of that object (since it is referenced from anywhere else), update that
172-
# replication and return it.
173170
with self._object_cache_lock:
174171
if obj.id in self._object_cache:
175-
old_obj = self._object_cache[obj.id]
176-
old_obj.update_from(obj)
177-
return old_obj
178-
self._object_cache[obj.id] = obj
172+
return self._object_cache[obj.id]
173+
self._object_cache[obj.id] = obj
179174
return obj
180175

181176
def get_item(self, identifier: model.Identifier) -> model.Identifiable:
@@ -186,6 +181,9 @@ def get_item(self, identifier: model.Identifier) -> model.Identifiable:
186181
:raises CouchDBError: If error occur during the request to the CouchDB server
187182
(see ``_do_request()`` for details)
188183
"""
184+
with self._object_cache_lock:
185+
if identifier in self._object_cache:
186+
return self._object_cache[identifier]
189187
try:
190188
return self.get_identifiable_by_couchdb_id(self._transform_id(identifier, False))
191189
except KeyError as e:
@@ -220,6 +218,37 @@ def add(self, x: model.Identifiable) -> None:
220218
with self._object_cache_lock:
221219
self._object_cache[x.id] = x
222220

221+
def commit(self, x: model.Identifiable) -> None:
222+
"""
223+
Write the current in-memory state of a stored object back to the CouchDB.
224+
225+
:param x: The object to persist
226+
:raises KeyError: If the object is not present in the store or no revision is known
227+
:raises CouchDBConflictError: If the object was modified in the database since it was last fetched
228+
:raises CouchDBError: If error occur during the request to the CouchDB server
229+
(see ``_do_request()`` for details)
230+
"""
231+
doc_url = "{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id))
232+
rev = get_couchdb_revision(doc_url)
233+
if rev is None:
234+
raise KeyError("No revision found for object with id {} — not fetched from this store".format(x.id))
235+
data = json.dumps({'data': x}, cls=json_serialization.AASToJsonEncoder)
236+
try:
237+
response = self._do_request(
238+
"{}?rev={}".format(doc_url, rev),
239+
'PUT',
240+
{'Content-type': 'application/json'},
241+
data.encode('utf-8'))
242+
set_couchdb_revision(doc_url, response["rev"])
243+
except CouchDBServerError as e:
244+
if e.code == 404:
245+
raise KeyError("No AAS object with id {} exists in CouchDB database".format(x.id)) from e
246+
elif e.code == 409:
247+
raise CouchDBConflictError(
248+
"Object with id {} has been modified in the database since it was last fetched."
249+
.format(x.id)) from e
250+
raise
251+
223252
def discard(self, x: model.Identifiable, safe_delete=False) -> None:
224253
"""
225254
Delete an :class:`~basyx.aas.model.base.Identifiable` AAS object from the CouchDB database

0 commit comments

Comments
 (0)