From 1f8d6e8412edef5177a4114203fdedb00faa555e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 19:08:19 +0000 Subject: [PATCH 1/2] Fix _serialize_depends_on to handle bare dict depends_on MATLAB's jsonencode converts single-element cell arrays to scalars, so depends_on can arrive as a bare dict instead of a list. Normalize it to a single-element list in both _serialize_depends_on and doc_to_sql for defense in depth. https://claude.ai/code/session_01UbcbwwRqiY8mCMRf2pEsWg --- src/did/implementations/doc2sql.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/did/implementations/doc2sql.py b/src/did/implementations/doc2sql.py index cb25f89..72ef97a 100644 --- a/src/did/implementations/doc2sql.py +++ b/src/did/implementations/doc2sql.py @@ -89,6 +89,8 @@ def _get_superclass_str(doc_props): def _serialize_depends_on(doc_props): """Serialize depends_on matching MATLAB's format: 'name,value;name,value;'""" depends_on = doc_props.get("depends_on", []) + if isinstance(depends_on, dict): + depends_on = [depends_on] if not depends_on or not isinstance(depends_on, list): return "" @@ -153,6 +155,11 @@ def doc_to_sql(doc): """ doc_props = doc.document_properties + # Normalize bare dict depends_on to a list (MATLAB's jsonencode converts + # single-element cell arrays to scalars). + if isinstance(doc_props.get("depends_on"), dict): + doc_props["depends_on"] = [doc_props["depends_on"]] + # Build the 'meta' table meta = {"name": "meta", "columns": []} From 9d5b7b01aab13282c2aa48c3de3e582a48c21456 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 19:13:08 +0000 Subject: [PATCH 2/2] Fix bare dict handling for file_info and depends_on across codebase MATLAB's jsonencode converts single-element cell arrays to scalars. This affected multiple code paths beyond _serialize_depends_on: - document.py: Add _normalize_file_info() and _ensure_depends_on_list() helpers. Use them in is_in_file_list, add_file, remove_file, dependency_value, and set_dependency_value. - sqlitedb.py: Extend _matlab_compatible_props to unwrap single-element file_info lists for MATLAB compatibility. - sqlitedb.py: Extend _normalize_loaded_props to re-wrap bare dict file_info back to lists on load. https://claude.ai/code/session_01UbcbwwRqiY8mCMRf2pEsWg --- src/did/document.py | 32 +++++++++++++++++++++++++---- src/did/implementations/sqlitedb.py | 18 +++++++++++++++- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/did/document.py b/src/did/document.py index 08fa340..e453f8d 100644 --- a/src/did/document.py +++ b/src/did/document.py @@ -53,10 +53,22 @@ def _reset_file_info(self): datastructures.empty_struct("name", "locations") ) + @staticmethod + def _normalize_file_info(file_info): + """Normalize file_info to a list. + + MATLAB's jsonencode converts single-element cell arrays to scalars, + so file_info may arrive as a bare dict instead of a list. + """ + if isinstance(file_info, dict): + return [file_info] if file_info else [] + if not isinstance(file_info, list): + return [] + return file_info + def is_in_file_list(self, filename): file_info = self.document_properties.get("files", {}).get("file_info", []) - if isinstance(file_info, dict) and not file_info: - file_info = [] + file_info = self._normalize_file_info(file_info) for i, info in enumerate(file_info): if info.get("name") == filename: @@ -71,8 +83,7 @@ def add_file(self, filename, location): if "file_info" not in files_prop: files_prop["file_info"] = [] - if isinstance(files_prop["file_info"], dict) and not files_prop["file_info"]: - files_prop["file_info"] = [] + files_prop["file_info"] = self._normalize_file_info(files_prop["file_info"]) file_info_list = files_prop["file_info"] @@ -82,6 +93,11 @@ def add_file(self, filename, location): file_info_list.append(new_info) def remove_file(self, filename): + files_prop = self.document_properties.get("files") + if files_prop is not None: + files_prop["file_info"] = self._normalize_file_info( + files_prop.get("file_info", []) + ) is_in, _, index = self.is_in_file_list(filename) if is_in: del self.document_properties["files"]["file_info"][index] @@ -138,7 +154,14 @@ def _normalize_to_document_class(data): } return data + def _ensure_depends_on_list(self): + """Normalize depends_on to a list if it is a bare dict.""" + dep = self.document_properties.get("depends_on") + if isinstance(dep, dict): + self.document_properties["depends_on"] = [dep] + def dependency_value(self, dependency_name, error_if_not_found=True): + self._ensure_depends_on_list() if "depends_on" in self.document_properties: for dep in self.document_properties["depends_on"]: if dep.get("name") == dependency_name: @@ -149,6 +172,7 @@ def dependency_value(self, dependency_name, error_if_not_found=True): return None def set_dependency_value(self, dependency_name, value, error_if_not_found=True): + self._ensure_depends_on_list() if "depends_on" in self.document_properties: for dep in self.document_properties["depends_on"]: if dep.get("name") == dependency_name: diff --git a/src/did/implementations/sqlitedb.py b/src/did/implementations/sqlitedb.py index 2a20520..07245cf 100644 --- a/src/did/implementations/sqlitedb.py +++ b/src/did/implementations/sqlitedb.py @@ -251,11 +251,19 @@ def _matlab_compatible_props(props): if isinstance(dep, list) and len(dep) == 1: props["depends_on"] = dep[0] + # Unwrap files.file_info + files = props.get("files") + if isinstance(files, dict): + fi = files.get("file_info") + if isinstance(fi, list): + if len(fi) == 1: + files["file_info"] = fi[0] + return props @staticmethod def _normalize_loaded_props(props): - """Ensure superclasses and depends_on are always lists. + """Ensure superclasses, depends_on, file_info, and locations are always lists. Inverse of _matlab_compatible_props. Mutates and returns props. """ @@ -268,6 +276,14 @@ def _normalize_loaded_props(props): if dep is not None and not isinstance(dep, list): props["depends_on"] = [dep] + # Re-wrap files.file_info (but not locations, which may be a bare dict + # from add_file in the Python API) + files = props.get("files") + if isinstance(files, dict): + fi = files.get("file_info") + if isinstance(fi, dict): + files["file_info"] = [fi] + return props def _do_add_doc(self, document_obj, branch_id, **kwargs):