Skip to content

Commit 4d8ebf1

Browse files
Merge pull request #47 from Waltham-Data-Science/claude/fix-sqlite-dependencies-6hZou
Normalize bare dict depends_on to list for MATLAB compatibility
2 parents c725edf + 37c1ac9 commit 4d8ebf1

3 files changed

Lines changed: 99 additions & 0 deletions

File tree

src/ndi/document.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,14 @@ def __init__(self, document_type: Union[str, dict, "ndi_document"] = "base", **k
7373
if isinstance(document_type, dict):
7474
# Loading from existing properties
7575
self._document_properties = deepcopy(document_type)
76+
self._normalize_depends_on()
7677
elif isinstance(document_type, ndi_document):
7778
# Copy from another ndi_document
7879
self._document_properties = deepcopy(document_type.document_properties)
7980
elif DIDDocument is not None and isinstance(document_type, DIDDocument):
8081
# Convert from DIDDocument
8182
self._document_properties = deepcopy(document_type.document_properties)
83+
self._normalize_depends_on()
8284
else:
8385
# Create new from schema
8486
self._document_properties = self.read_blank_definition(document_type)
@@ -92,6 +94,27 @@ def __init__(self, document_type: Union[str, dict, "ndi_document"] = "base", **k
9294
for key, value in kwargs.items():
9395
self._set_nested_property(key, value)
9496

97+
def _normalize_depends_on(self):
98+
"""Ensure depends_on and files.file_info are always lists.
99+
100+
MATLAB's jsonencode converts single-element cell arrays to scalars,
101+
so documents downloaded from the cloud (or created by MATLAB) may
102+
have ``depends_on`` or ``files.file_info`` as a bare dict instead
103+
of a list. Normalizing here guarantees that downstream code
104+
(including DID-python's ``doc_to_sql`` / ``_serialize_depends_on``
105+
and all NDI code that iterates over ``file_info``) always sees
106+
a list.
107+
"""
108+
dep = self._document_properties.get("depends_on")
109+
if isinstance(dep, dict):
110+
self._document_properties["depends_on"] = [dep]
111+
112+
files = self._document_properties.get("files")
113+
if isinstance(files, dict):
114+
fi = files.get("file_info")
115+
if isinstance(fi, dict):
116+
files["file_info"] = [fi]
117+
95118
@property
96119
def document_properties(self) -> dict:
97120
"""The document's properties as a nested dictionary."""

tests/test_database.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,51 @@ def test_find_dependencies(self, temp_session):
388388
assert deps[0].id == parent_ido.id
389389

390390

391+
class TestDatabaseDependsSQLite:
392+
"""Test that depends_on is correctly stored in SQLite doc_data table."""
393+
394+
def test_depends_on_bare_dict_stored_in_doc_data(self, temp_session):
395+
"""Test that a bare dict depends_on (MATLAB-style) is stored in doc_data."""
396+
db = ndi_database(temp_session)
397+
398+
parent_ido = ndi_ido()
399+
parent = ndi_document(
400+
{
401+
"base": {
402+
"id": parent_ido.id,
403+
"datestamp": timestamp(),
404+
"name": "parent",
405+
"session_id": "",
406+
},
407+
"document_class": {"class_name": "base", "superclasses": []},
408+
}
409+
)
410+
db.add(parent)
411+
412+
child_ido = ndi_ido()
413+
# Use bare dict (MATLAB-style) for depends_on
414+
child = ndi_document(
415+
{
416+
"base": {
417+
"id": child_ido.id,
418+
"datestamp": timestamp(),
419+
"name": "child",
420+
"session_id": "",
421+
},
422+
"document_class": {"class_name": "base", "superclasses": []},
423+
"depends_on": {"name": "parent_doc", "value": parent_ido.id},
424+
}
425+
)
426+
db.add(child)
427+
428+
# Verify depends_on was stored in doc_data by querying with depends_on
429+
from ndi.query import ndi_query
430+
431+
results = db.search(ndi_query("").depends_on("parent_doc", parent_ido.id))
432+
assert len(results) == 1
433+
assert results[0].id == child_ido.id
434+
435+
391436
class TestDatabasePaths:
392437
"""Test ndi_database path properties."""
393438

tests/test_document.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,37 @@ def test_dependency_with_deps(self):
178178
assert "element_id" in names
179179
assert len(deps) == 2
180180

181+
def test_depends_on_bare_dict_normalized_to_list(self):
182+
"""Test that a bare dict depends_on (MATLAB-style) is normalized to a list."""
183+
props = {
184+
"base": {"id": "id", "datestamp": "", "session_id": ""},
185+
"document_class": {"class_name": "base", "superclasses": []},
186+
"depends_on": {"name": "element_id", "value": "abc123"},
187+
}
188+
doc = ndi_document(props)
189+
# Should be normalized to a list
190+
assert isinstance(doc.document_properties["depends_on"], list)
191+
assert len(doc.document_properties["depends_on"]) == 1
192+
assert doc.dependency_value("element_id") == "abc123"
193+
194+
def test_file_info_bare_dict_normalized_to_list(self):
195+
"""Test that a bare dict file_info (MATLAB-style) is normalized to a list."""
196+
props = {
197+
"base": {"id": "id", "datestamp": "", "session_id": ""},
198+
"document_class": {"class_name": "base", "superclasses": []},
199+
"files": {
200+
"file_info": {
201+
"name": "data.bin",
202+
"locations": {"location": "/tmp/data.bin"},
203+
}
204+
},
205+
}
206+
doc = ndi_document(props)
207+
fi = doc.document_properties["files"]["file_info"]
208+
assert isinstance(fi, list)
209+
assert len(fi) == 1
210+
assert fi[0]["name"] == "data.bin"
211+
181212
def test_dependency_value(self):
182213
"""Test dependency_value returns correct value."""
183214
props = {

0 commit comments

Comments
 (0)