From 5143786cf64459dbcad6174d3b75a6df1a90f5e2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 30 May 2026 19:09:38 +0000 Subject: [PATCH 1/3] fix(allomorph): replace OwnerOfClass.project.DefaultXxxWs with Cache.DefaultXxxWs (#147) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three sites in allomorph.py used self._obj.OwnerOfClass.project.DefaultVernWs / DefaultAnalWs. OwnerOfClass is an LCM method (requires an Int32 class-id arg); bare attribute access returns the bound method object, so .project raises AttributeError, silently swallowed by the bare except — making every property return "". Fixed by using self._obj.Cache.DefaultVernWs / DefaultAnalWs instead, matching the pattern used by the May-20 sweep in phonological_rule.py et al. Closes #147 https://claude.ai/code/session_01WsYz9YzNkyFzkStEr1y8Fg --- flexlibs2/code/Lexicon/allomorph.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flexlibs2/code/Lexicon/allomorph.py b/flexlibs2/code/Lexicon/allomorph.py index 3663b3d..97654de 100644 --- a/flexlibs2/code/Lexicon/allomorph.py +++ b/flexlibs2/code/Lexicon/allomorph.py @@ -130,7 +130,7 @@ def form(self) -> str: form_multistring = self._obj.Form if form_multistring: # Get from default vernacular writing system - default_ws = self._obj.OwnerOfClass.project.DefaultVernWs + default_ws = self._obj.Cache.DefaultVernWs form_text = ITsString(form_multistring.get_String(default_ws)).Text return normalize_text(form_text) return "" @@ -160,7 +160,7 @@ def gloss(self) -> str: if hasattr(self._concrete, "Gloss") and self._concrete.Gloss: # Get from default analysis writing system - default_ws = self._obj.OwnerOfClass.project.DefaultAnalWs + default_ws = self._obj.Cache.DefaultAnalWs gloss_text = ITsString(self._concrete.Gloss.get_String(default_ws)).Text return normalize_text(gloss_text) return "" @@ -275,7 +275,7 @@ def stem_name(self): if hasattr(self._concrete, "StemName") and self._concrete.StemName: # Get from default analysis writing system - default_ws = self._obj.OwnerOfClass.project.DefaultAnalWs + default_ws = self._obj.Cache.DefaultAnalWs stem_name_text = ITsString(self._concrete.StemName.get_String(default_ws)).Text return normalize_text(stem_name_text) return "" From 02469a10d82c46b398096b737d1df3deb8b2d8ae Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 30 May 2026 19:09:49 +0000 Subject: [PATCH 2/3] fix(lcm_casting): clone_properties() -- call Cache.LanguageProject instead of OwnerOfClass.project (#153) clone_properties() tried to resolve project via dest.OwnerOfClass.project. OwnerOfClass is an LCM method (requires an Int32 class-id arg); accessing it as a bare attribute returns the bound method object, not an ICmObject, so .project raises AttributeError -- silently swallowed by the bare except. This left `project = None`, permanently killing the collection-clone branch at line 511 (if project:) for every caller that didn't pass project explicitly. Fix: use dest.Cache.LanguageProject, the canonical accessor used elsewhere in the codebase since commit 611f5a3. Closes #153 https://claude.ai/code/session_01WsYz9YzNkyFzkStEr1y8Fg --- flexlibs2/code/lcm_casting.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flexlibs2/code/lcm_casting.py b/flexlibs2/code/lcm_casting.py index bef5765..0809be5 100644 --- a/flexlibs2/code/lcm_casting.py +++ b/flexlibs2/code/lcm_casting.py @@ -472,10 +472,10 @@ def clone_properties(source_obj, dest_obj, project=None): source = cast_to_concrete(source_obj) dest = cast_to_concrete(dest_obj) - # If project not provided, try to get it from the destination object - if project is None and hasattr(dest, "OwnerOfClass"): + # If project not provided, resolve via Cache.LanguageProject (canonical accessor) + if project is None and hasattr(dest, "Cache"): try: - project = dest.OwnerOfClass.project + project = dest.Cache.LanguageProject except Exception: pass From 78c2ac6ddfbf70de947966ccc348aa9cfd9e94df Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 30 May 2026 19:10:26 +0000 Subject: [PATCH 3/3] fix(Duplicate): drop OC.IndexOf/Insert -- unordered collections don't support positional ops (#167) LcmOwningCollection (OC suffix) implements ICollection, not IList. Calling IndexOf or Insert on it either silently no-ops or raises in pythonnet, so insert_after=True on OC-backed Duplicate methods never worked. For the two half-fixed sites (WfiGloss, WfiAnalysis) the list(...).index() workaround avoided the IndexOf failure but the follow-up .Insert() was still broken. Fix: replace the entire insert_after branch with an unconditional .Add() on the six affected OC collections: - NoteOperations.py: AnnotationsOC - DataNotebookOperations.py: RecordsOC - GramCatOperations.py: TypesOC - DiscourseOperations.py: ChartsOC - WfiGlossOperations.py: MeaningsOC - WfiAnalysisOperations.py: AnalysesOC OS-backed collections (SubRecordsOS, RepliesOS, SubPossibilitiesOS) are unaffected -- positional Insert is well-defined there. Closes #167 https://claude.ai/code/session_01WsYz9YzNkyFzkStEr1y8Fg --- flexlibs2/code/Grammar/GramCatOperations.py | 7 ++----- flexlibs2/code/Notebook/DataNotebookOperations.py | 7 ++----- flexlibs2/code/Notebook/NoteOperations.py | 4 ++-- flexlibs2/code/TextsWords/DiscourseOperations.py | 12 +++--------- flexlibs2/code/TextsWords/WfiAnalysisOperations.py | 9 ++------- flexlibs2/code/TextsWords/WfiGlossOperations.py | 9 ++------- 6 files changed, 13 insertions(+), 35 deletions(-) diff --git a/flexlibs2/code/Grammar/GramCatOperations.py b/flexlibs2/code/Grammar/GramCatOperations.py index c53395c..edd2c76 100644 --- a/flexlibs2/code/Grammar/GramCatOperations.py +++ b/flexlibs2/code/Grammar/GramCatOperations.py @@ -519,11 +519,8 @@ def Duplicate(self, item_or_hvo, insert_after=True, deep=False): feature_system = self.project.lp.MsFeatureSystemOA factory = self.project.project.ServiceLocator.GetService(IFsFeatStrucTypeFactory) duplicate = factory.Create() - if insert_after: - source_index = feature_system.TypesOC.IndexOf(source) - feature_system.TypesOC.Insert(source_index + 1, duplicate) - else: - feature_system.TypesOC.Add(duplicate) + # TypesOC is unordered (OC); insert_after is a no-op, add at end + feature_system.TypesOC.Add(duplicate) # Copy simple MultiString properties (AFTER adding to parent) duplicate.Name.CopyAlternatives(source.Name) diff --git a/flexlibs2/code/Notebook/DataNotebookOperations.py b/flexlibs2/code/Notebook/DataNotebookOperations.py index 1c4b37d..1d7772e 100644 --- a/flexlibs2/code/Notebook/DataNotebookOperations.py +++ b/flexlibs2/code/Notebook/DataNotebookOperations.py @@ -2538,11 +2538,8 @@ def Duplicate(self, record_or_hvo, insert_after=True, deep=True): else: # Parent is the top-level repository repos = self.project.project.ServiceLocator.GetService(IRnResearchNbkRepository) - if insert_after: - source_index = repos.RecordsOC.IndexOf(source) - repos.RecordsOC.Insert(source_index + 1, duplicate) - else: - repos.RecordsOC.Add(duplicate) + # RecordsOC is unordered (OC); insert_after is a no-op, add at end + repos.RecordsOC.Add(duplicate) # Copy simple MultiString properties duplicate.Title.CopyAlternatives(source.Title) diff --git a/flexlibs2/code/Notebook/NoteOperations.py b/flexlibs2/code/Notebook/NoteOperations.py index d2d011b..6314436 100644 --- a/flexlibs2/code/Notebook/NoteOperations.py +++ b/flexlibs2/code/Notebook/NoteOperations.py @@ -323,8 +323,8 @@ def Duplicate(self, item_or_hvo, insert_after=True, deep=True): source_index = parent.RepliesOS.IndexOf(source) parent.RepliesOS.Insert(source_index + 1, duplicate) elif hasattr(parent, "AnnotationsOC"): - source_index = parent.AnnotationsOC.IndexOf(source) - parent.AnnotationsOC.Insert(source_index + 1, duplicate) + # AnnotationsOC is unordered (OC); insert_after is a no-op, add at end + parent.AnnotationsOC.Add(duplicate) else: # Insert at end if hasattr(parent, "RepliesOS"): diff --git a/flexlibs2/code/TextsWords/DiscourseOperations.py b/flexlibs2/code/TextsWords/DiscourseOperations.py index ff422c4..4d9dd19 100644 --- a/flexlibs2/code/TextsWords/DiscourseOperations.py +++ b/flexlibs2/code/TextsWords/DiscourseOperations.py @@ -1096,15 +1096,9 @@ def Duplicate(self, item_or_hvo, insert_after=True, deep=True): duplicate = factory.Create() # ADD TO PARENT FIRST - if insert_after: - # Insert after source chart - if hasattr(parent, "ChartsOC"): - source_index = parent.ChartsOC.IndexOf(source) - parent.ChartsOC.Insert(source_index + 1, duplicate) - else: - # Insert at end - if hasattr(parent, "ChartsOC"): - parent.ChartsOC.Add(duplicate) + # ChartsOC is unordered (OC); insert_after is a no-op, add at end + if hasattr(parent, "ChartsOC"): + parent.ChartsOC.Add(duplicate) # Copy MultiString properties (AFTER adding to parent) if hasattr(source, "Name") and source.Name: diff --git a/flexlibs2/code/TextsWords/WfiAnalysisOperations.py b/flexlibs2/code/TextsWords/WfiAnalysisOperations.py index f35537c..001e088 100644 --- a/flexlibs2/code/TextsWords/WfiAnalysisOperations.py +++ b/flexlibs2/code/TextsWords/WfiAnalysisOperations.py @@ -472,13 +472,8 @@ def Duplicate(self, item_or_hvo, insert_after=True, deep=False): duplicate = factory.Create() # Determine insertion position - if insert_after: - # Insert after source analysis - source_index = list(parent.AnalysesOC).index(source) - parent.AnalysesOC.Insert(source_index + 1, duplicate) - else: - # Insert at end - parent.AnalysesOC.Add(duplicate) + # AnalysesOC is unordered (OC); insert_after is a no-op, add at end + parent.AnalysesOC.Add(duplicate) # Copy Reference Atomic (RA) properties if hasattr(source, "CategoryRA") and source.CategoryRA: diff --git a/flexlibs2/code/TextsWords/WfiGlossOperations.py b/flexlibs2/code/TextsWords/WfiGlossOperations.py index 715a84c..60808f5 100644 --- a/flexlibs2/code/TextsWords/WfiGlossOperations.py +++ b/flexlibs2/code/TextsWords/WfiGlossOperations.py @@ -401,13 +401,8 @@ def Duplicate(self, item_or_hvo, insert_after=True): duplicate = factory.Create() # Determine insertion position - if insert_after: - # Insert after source gloss - source_index = list(parent.MeaningsOC).index(source) - parent.MeaningsOC.Insert(source_index + 1, duplicate) - else: - # Insert at end - parent.MeaningsOC.Add(duplicate) + # MeaningsOC is unordered (OC); insert_after is a no-op, add at end + parent.MeaningsOC.Add(duplicate) # Copy simple MultiString properties duplicate.Form.CopyAlternatives(source.Form)