pyflexicon 4.1.2__tar.gz → 4.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/CHANGELOG.md +59 -0
  2. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/PKG-INFO +1 -1
  3. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/BaseOperations.py +80 -11
  4. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Discourse/ConstChartOperations.py +2 -2
  5. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/FLExProject.py +47 -56
  6. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/MorphRuleOperations.py +9 -3
  7. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/AllomorphOperations.py +26 -40
  8. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/LexEntryOperations.py +23 -49
  9. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/LexReferenceOperations.py +6 -3
  10. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/LexSenseOperations.py +11 -24
  11. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/MSAOperations.py +26 -9
  12. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/VariantOperations.py +1 -0
  13. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/TranslationTypeOperations.py +2 -1
  14. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/DataNotebookOperations.py +1 -1
  15. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/NoteOperations.py +1 -0
  16. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/PersonOperations.py +2 -2
  17. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Reversal/ReversalIndexOperations.py +3 -2
  18. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Scripture/ScrBookOperations.py +2 -2
  19. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Scripture/ScrDraftOperations.py +2 -2
  20. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Shared/FilterOperations.py +3 -2
  21. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Shared/MediaOperations.py +77 -17
  22. pyflexicon-4.2.0/flexicon/code/Shared/morph_type_utils.py +177 -0
  23. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Shared/string_utils.py +81 -0
  24. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/AnnotationDefOperations.py +1 -0
  25. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/CheckOperations.py +3 -1
  26. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/WritingSystemOperations.py +2 -0
  27. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/DiscourseOperations.py +1 -0
  28. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/ParagraphOperations.py +3 -2
  29. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/SegmentOperations.py +52 -1
  30. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/TextOperations.py +1 -1
  31. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/WfiAnalysisOperations.py +120 -0
  32. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/WfiGlossOperations.py +75 -66
  33. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/WordformOperations.py +6 -4
  34. pyflexicon-4.2.0/flexicon/code/_op_aliases.py +156 -0
  35. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/transaction.py +13 -0
  36. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/pyflexicon.egg-info/PKG-INFO +1 -1
  37. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/pyflexicon.egg-info/SOURCES.txt +4 -0
  38. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/pyproject.toml +1 -1
  39. pyflexicon-4.2.0/tests/test_flexproject_aliases.py +171 -0
  40. pyflexicon-4.2.0/tests/test_wrap_enumerable_pattern.py +247 -0
  41. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/LICENSE.txt +0 -0
  42. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/MANIFEST.in +0 -0
  43. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/NOTICE +0 -0
  44. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/README.rst +0 -0
  45. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/__init__.py +0 -0
  46. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/__init__.pyi +0 -0
  47. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/BaseOperations.pyi +0 -0
  48. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Discourse/ConstChartCellTagOperations.py +0 -0
  49. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Discourse/ConstChartClauseMarkerOperations.py +0 -0
  50. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Discourse/ConstChartMarkerOperations.py +0 -0
  51. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Discourse/ConstChartMovedTextOperations.py +0 -0
  52. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Discourse/ConstChartRowOperations.py +0 -0
  53. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Discourse/ConstChartWordGroupOperations.py +0 -0
  54. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Discourse/__init__.py +0 -0
  55. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/FLExGlobals.py +0 -0
  56. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/FLExInit.py +0 -0
  57. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/FLExLCM.py +0 -0
  58. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/FLExProject.pyi +0 -0
  59. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/AllomorphOperations.pyi +0 -0
  60. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/EnvironmentOperations.py +0 -0
  61. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/EnvironmentOperations.pyi +0 -0
  62. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/GramCatOperations.py +0 -0
  63. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/GramCatOperations.pyi +0 -0
  64. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/InflectionFeatureOperations.py +0 -0
  65. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/InflectionFeatureOperations.pyi +0 -0
  66. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/MorphRuleOperations.pyi +0 -0
  67. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/NaturalClassOperations.py +0 -0
  68. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/NaturalClassOperations.pyi +0 -0
  69. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/POSOperations.py +0 -0
  70. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/POSOperations.pyi +0 -0
  71. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/PhonFeatureOperations.py +0 -0
  72. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/PhonemeOperations.py +0 -0
  73. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/PhonemeOperations.pyi +0 -0
  74. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/PhonologicalRuleOperations.py +0 -0
  75. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/PhonologicalRuleOperations.pyi +0 -0
  76. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/StratumOperations.py +0 -0
  77. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/__init__.py +0 -0
  78. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/adhoc_prohibition.py +0 -0
  79. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/affix_template.py +0 -0
  80. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/affix_template_collection.py +0 -0
  81. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/compound_rule.py +0 -0
  82. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/compound_rule_collection.py +0 -0
  83. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/phonological_rule.py +0 -0
  84. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/prohibition_collection.py +0 -0
  85. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Grammar/rule_collection.py +0 -0
  86. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/EtymologyOperations.py +0 -0
  87. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/EtymologyOperations.pyi +0 -0
  88. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/ExampleOperations.py +0 -0
  89. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/ExampleOperations.pyi +0 -0
  90. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/LexEntryOperations.pyi +0 -0
  91. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/LexReferenceOperations.pyi +0 -0
  92. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/LexSenseOperations.pyi +0 -0
  93. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/PronunciationOperations.py +0 -0
  94. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/PronunciationOperations.pyi +0 -0
  95. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/SemanticDomainOperations.py +0 -0
  96. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/SemanticDomainOperations.pyi +0 -0
  97. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/VariantOperations.pyi +0 -0
  98. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/__init__.py +0 -0
  99. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/allomorph.py +0 -0
  100. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/allomorph_collection.py +0 -0
  101. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/morphosyntax_analysis.py +0 -0
  102. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lexicon/msa_collection.py +0 -0
  103. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/AgentOperations.py +0 -0
  104. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/AgentOperations.pyi +0 -0
  105. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/ConfidenceOperations.py +0 -0
  106. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/ConfidenceOperations.pyi +0 -0
  107. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/LocalizedListsOperations.py +0 -0
  108. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/OverlayOperations.py +0 -0
  109. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/OverlayOperations.pyi +0 -0
  110. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/PossibilityListOperations.py +0 -0
  111. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/PossibilityListOperations.pyi +0 -0
  112. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/PublicationOperations.py +0 -0
  113. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/PublicationOperations.pyi +0 -0
  114. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/TranslationTypeOperations.pyi +0 -0
  115. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/__init__.py +0 -0
  116. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Lists/possibility_item_base.py +0 -0
  117. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/AnthropologyOperations.py +0 -0
  118. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/AnthropologyOperations.pyi +0 -0
  119. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/DataNotebookOperations.pyi +0 -0
  120. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/LocationOperations.py +0 -0
  121. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/LocationOperations.pyi +0 -0
  122. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/NoteOperations.pyi +0 -0
  123. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/PersonOperations.pyi +0 -0
  124. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/__init__.py +0 -0
  125. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/annotation.py +0 -0
  126. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Notebook/annotation_collection.py +0 -0
  127. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/PythonicWrapper.py +0 -0
  128. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Reversal/ReversalIndexEntryOperations.py +0 -0
  129. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Reversal/__init__.py +0 -0
  130. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Scripture/ScrAnnotationsOperations.py +0 -0
  131. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Scripture/ScrNoteOperations.py +0 -0
  132. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Scripture/ScrSectionOperations.py +0 -0
  133. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Scripture/ScrTxtParaOperations.py +0 -0
  134. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Scripture/__init__.py +0 -0
  135. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Shared/__init__.py +0 -0
  136. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Shared/catalog.py +0 -0
  137. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Shared/catalog_backed.py +0 -0
  138. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Shared/lcm_constants.py +0 -0
  139. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Shared/rule_patterns.py +0 -0
  140. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Shared/smart_collection.py +0 -0
  141. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/Shared/wrapper_base.py +0 -0
  142. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/AnnotationDefOperations.pyi +0 -0
  143. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/CheckOperations.pyi +0 -0
  144. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/CustomFieldOperations.py +0 -0
  145. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/CustomFieldOperations.pyi +0 -0
  146. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/ProjectSettingsOperations.py +0 -0
  147. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/ProjectSettingsOperations.pyi +0 -0
  148. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/WritingSystemOperations.pyi +0 -0
  149. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/__init__.py +0 -0
  150. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/context_collection.py +0 -0
  151. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/System/phonological_context.py +0 -0
  152. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/DiscourseOperations.pyi +0 -0
  153. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/FilterOperations.pyi +0 -0
  154. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/MediaOperations.pyi +0 -0
  155. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/ParagraphOperations.pyi +0 -0
  156. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/SegmentOperations.pyi +0 -0
  157. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/TextOperations.pyi +0 -0
  158. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/WfiAnalysisOperations.pyi +0 -0
  159. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/WfiGlossOperations.pyi +0 -0
  160. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/WfiMorphBundleOperations.py +0 -0
  161. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/WfiMorphBundleOperations.pyi +0 -0
  162. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/WordformOperations.pyi +0 -0
  163. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/TextsWords/__init__.py +0 -0
  164. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/__init__.py +0 -0
  165. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/__init__.pyi +0 -0
  166. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/_context_manager_base.py +0 -0
  167. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/exceptions.py +0 -0
  168. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/lcm_casting.py +0 -0
  169. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/code/undoable_operation.py +0 -0
  170. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/examples/demo_lexicon.py +0 -0
  171. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/examples/demo_openproject.py +0 -0
  172. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/examples/demo_pos_operations.py +0 -0
  173. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/examples/demo_writing_systems.py +0 -0
  174. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/py.typed +0 -0
  175. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/__init__.py +0 -0
  176. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/conflict_resolvers.py +0 -0
  177. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/dependency_graph.py +0 -0
  178. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/dependency_resolver.py +0 -0
  179. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/diff.py +0 -0
  180. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/engine.py +0 -0
  181. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/export.py +0 -0
  182. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/hierarchical_importer.py +0 -0
  183. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/match_strategies.py +0 -0
  184. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/merge_ops.py +0 -0
  185. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/selective_import.py +0 -0
  186. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/tests/__init__.py +0 -0
  187. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/tests/test_base_operations.py +0 -0
  188. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/tests/test_dependency_graph.py +0 -0
  189. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/tests/test_diff_engine.py +0 -0
  190. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/tests/test_duplicate_operations.py +0 -0
  191. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/tests/test_match_strategies.py +0 -0
  192. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/tests/test_merge_ops.py +0 -0
  193. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/tests/test_selective_import.py +0 -0
  194. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/tests/test_sync_engine.py +0 -0
  195. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/tests/test_sync_result.py +0 -0
  196. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/tests/test_validation.py +0 -0
  197. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/sync/validation.py +0 -0
  198. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/tests/test_CustomFields.py +0 -0
  199. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/tests/test_FLExInit.py +0 -0
  200. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexicon/tests/test_FLExProject.py +0 -0
  201. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/flexlibs2/__init__.py +0 -0
  202. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/pyflexicon.egg-info/dependency_links.txt +0 -0
  203. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/pyflexicon.egg-info/requires.txt +0 -0
  204. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/pyflexicon.egg-info/top_level.txt +0 -0
  205. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/setup.cfg +0 -0
  206. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_affix_template_wrappers.py +0 -0
  207. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_allomorph_wrappers.py +0 -0
  208. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_annotation_wrappers.py +0 -0
  209. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_catalog.py +0 -0
  210. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_collections.py +0 -0
  211. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_compound_rule_wrappers.py +0 -0
  212. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_consolidation_coverage.py +0 -0
  213. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_context_wrappers.py +0 -0
  214. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_custom_field_create_refusal.py +0 -0
  215. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_exception_handling.py +0 -0
  216. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_fixtures.py +0 -0
  217. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_flexproject_discoverability.py +0 -0
  218. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_helpers.py +0 -0
  219. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_homographs.py +0 -0
  220. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_itsstring_fix.py +0 -0
  221. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_lcm_api_real.py +0 -0
  222. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_lcm_api_usage.py +0 -0
  223. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_lcm_direct.py +0 -0
  224. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_lcm_method_verification.py +0 -0
  225. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_msa_wrappers.py +0 -0
  226. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_normalize_match_key.py +0 -0
  227. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_normalize_ws_handle.py +0 -0
  228. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_operations_baseline.py +0 -0
  229. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_pattern_g_owner_of_class.py +0 -0
  230. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_pattern_writing_systems_enumeration.py +0 -0
  231. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_phoneme_duplicate_fix.py +0 -0
  232. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_phonological_rules_wrappers.py +0 -0
  233. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_prohibition_wrappers.py +0 -0
  234. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_rule_patterns.py +0 -0
  235. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_segment_baseline_text.py +0 -0
  236. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_sense_lookups.py +0 -0
  237. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_validation_base.py +0 -0
  238. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_wfianalysis_agent_import.py +0 -0
  239. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_wrappers.py +0 -0
  240. {pyflexicon-4.1.2 → pyflexicon-4.2.0}/tests/test_write_enabled_fix.py +0 -0
@@ -15,6 +15,65 @@ _Nothing yet. Non-breaking fixes and breaking changes accumulate here until the
15
15
 
16
16
  ---
17
17
 
18
+ ## [4.2.0]
19
+
20
+ ### Added
21
+ - **`MsaFactoryOperations.CreateStem` / `CreateUnclassifiedAffix`** now
22
+ accept `pos=None` for a category-less stem or unclassified-affix MSA
23
+ (a valid, blank-category-cell FLEx state), mirroring the existing
24
+ `CreateDerivAff` `to_pos=None` precedent. Unblocks cross-project
25
+ transfer of category-less MSAs (9c04ab8).
26
+
27
+ ### Fixed
28
+ - **`MsaFactoryOperations.CreateInflAff`** accepts `pos=None` for a
29
+ category-less inflectional affix, matching `CreateStem` /
30
+ `CreateUnclassifiedAffix` (6f316b3).
31
+ - **Morph-type classification** unified into a single shared module
32
+ (`Shared/morph_type_utils.py`); `LexEntryOperations`,
33
+ `AllomorphOperations`, and `LexSenseOperations` no longer each carry
34
+ independently drifting stem/affix GUID sets. Fixes a silent logical
35
+ inversion in `LexSenseOperations.__EntryHasAffixMorphType`'s GUID set.
36
+ `MorphRuleOperations.Duplicate` now defaults `deep=True`, matching the
37
+ LexEntry/Text family (#203, #213, #214).
38
+ - **`wrap_enumerable`** now catches bare Python iterators/generators (not
39
+ just raw C# `IEnumerable`), so `GetAll()`/`GetAnalyses()`-style methods
40
+ across ~20 Operations classes reliably return a subscriptable,
41
+ `len()`-able collection instead of raising `TypeError` on `entries[0]`
42
+ or `len(entries)` (#201).
43
+ - **`MediaOperations.Create`** now owns a new `CmFile` in the appropriate
44
+ `CmFolder` before setting `InternalPath`, fixing a
45
+ `NullReferenceException` that made the add-a-picture/media surface
46
+ (`AddPicture`, `CopyToProject`, `Create`, `AddMediaFile`) unusable.
47
+ `CopyToProject`'s `LinkedFilesRootDir` guard now reads from `ILangProject`
48
+ instead of the `LcmCache` (#226).
49
+ - **`GetCustomFieldValue`** on a bare `ITsMultiString` (no
50
+ `BestAnalysisVernacularAlternative` accessor) now resolves the best
51
+ alternative by writing-system priority instead of failing (#224).
52
+ Transaction entry no longer logs a misleading "NoneType is not
53
+ callable" warning when the LCM `Mark` API is unavailable; it logs at
54
+ debug level and proceeds without rollback capability (#221).
55
+ - **`BaseOperations._ValidateParam`** guards against stale/deleted LCM
56
+ objects (`IsValidObject is False`), raising `FP_ParameterError` naming
57
+ the parameter instead of an opaque `NullReferenceException` deep
58
+ inside LCM (#205).
59
+ - **`SegmentOperations.GetAnalyses`** / `WfiGlossOperations` — polymorphic
60
+ `IAnalysis` tokens (`IWfiWordform` / `IWfiAnalysis` / `IWfiGloss` /
61
+ `IPunctuationForm`) are now resolved by `ClassName` rather than
62
+ `isinstance`, which is unreliable against base-typed LCM refs. Adds
63
+ `SegmentOperations.GetGloss`, `WfiAnalysisOperations.GetCategoryAbbrev`,
64
+ and `GetMorphemeBundles` polymorphic helpers (#212).
65
+
66
+ ### Changed
67
+ - **`FLExProject`** singular/plural accessor aliases (e.g.
68
+ `InflectionFeature`/`InflectionFeatures`) are now generated from a
69
+ single table (`_op_aliases.py`, 58 aliases) instead of ~10 hand-written
70
+ properties, closing the gap where a guessed accessor name raised
71
+ `AttributeError`. All previously hand-written aliases are preserved;
72
+ each now emits a `DeprecationWarning` naming the canonical accessor
73
+ (#200).
74
+
75
+ ---
76
+
18
77
  ## [4.1.2]
19
78
 
20
79
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyflexicon
3
- Version: 4.1.2
3
+ Version: 4.2.0
4
4
  Summary: flexicon: a user-centric Python library for FieldWorks Language Explorer (FLEx) projects via the SIL Language and Culture Model (LCM) API
5
5
  Author-email: Matthew Lee <matthew_lee@sil.org>
6
6
  Maintainer-email: Matthew Lee <matthew_lee@sil.org>
@@ -117,12 +117,64 @@ class EnumerableWrapper:
117
117
  return f"EnumerableWrapper({self._enumerable})"
118
118
 
119
119
 
120
+ def _needs_enumerable_wrap(result):
121
+ """
122
+ Decide whether `result` needs to be wrapped in EnumerableWrapper.
123
+
124
+ Historically this only checked for raw C# IEnumerable objects (those
125
+ exposing `GetEnumerator`). That missed two common return shapes used
126
+ throughout the Operations classes:
127
+
128
+ - `self.project.ObjectsIn(...)`, which already calls Python's
129
+ built-in `iter()` on the underlying C# enumerable before returning
130
+ it. The resulting object exposes `__next__`/`__iter__` but *not*
131
+ `GetEnumerator`, so it slipped past the old check untouched.
132
+ - Generator-function GetAll()/GetAnalyses() implementations (using
133
+ `yield`), which return a plain Python generator object -- also has
134
+ `__next__` but no `GetEnumerator`, and is not subscriptable and
135
+ has no `len()`.
136
+
137
+ Both shapes produced the exact bug in issue #201: callers got back a
138
+ bare iterator/generator that looked list-like but raised
139
+ `TypeError` on `entries[0]` or `len(entries)`.
140
+
141
+ Anything that already supports both indexing and `len()` (plain
142
+ lists, tuples, existing EnumerableWrapper/SmartCollection instances,
143
+ etc.) is left alone -- there's nothing to fix and no need to
144
+ re-wrap it.
145
+
146
+ Args:
147
+ result: The raw value returned by the wrapped method.
148
+
149
+ Returns:
150
+ bool: True if `result` should be wrapped in EnumerableWrapper.
151
+ """
152
+ if result is None:
153
+ return False
154
+ if isinstance(result, EnumerableWrapper):
155
+ return False
156
+ # Raw C# IEnumerable (pythonnet objects exposing GetEnumerator).
157
+ if hasattr(result, "GetEnumerator"):
158
+ return True
159
+ # Plain Python iterator/generator (has __next__) that does not
160
+ # already behave like a sequence (no __len__ + __getitem__ pair).
161
+ # This covers both `iter(repo.AllInstances())` (from ObjectsIn())
162
+ # and generator-function bodies using `yield`.
163
+ if hasattr(result, "__next__"):
164
+ already_sequence = hasattr(result, "__len__") and hasattr(result, "__getitem__")
165
+ if not already_sequence:
166
+ return True
167
+ return False
168
+
169
+
120
170
  class wrap_enumerable:
121
171
  """
122
- Descriptor to automatically wrap IEnumerable return values.
172
+ Descriptor to automatically wrap IEnumerable/iterator return values.
123
173
 
124
- Wraps methods that return C# IEnumerable collections to make them
125
- Pythonic with .Count property and indexing support.
174
+ Wraps methods that return C# IEnumerable collections -- or plain
175
+ Python iterators/generators built on top of them (e.g. via
176
+ `self.project.ObjectsIn(...)` or a `yield`-based method body) -- to
177
+ make them Pythonic with `.Count`, `len()`, and indexing support.
126
178
 
127
179
  Must be a descriptor to properly delegate to OperationsMethod's __get__,
128
180
  ensuring the descriptor protocol works correctly when stacked decorators
@@ -140,6 +192,7 @@ class wrap_enumerable:
140
192
  items = GetAll(project)
141
193
  count = items.Count # Works!
142
194
  first = items[0] # Works!
195
+ length = len(items) # Works!
143
196
  """
144
197
 
145
198
  def __init__(self, func):
@@ -169,8 +222,7 @@ class wrap_enumerable:
169
222
  # Return a wrapper that will wrap the result
170
223
  def wrapped_method(*args, **kwargs):
171
224
  result = inner_method(*args, **kwargs)
172
- # Only wrap if it looks like an IEnumerable (has GetEnumerator)
173
- if result is not None and hasattr(result, "GetEnumerator"):
225
+ if _needs_enumerable_wrap(result):
174
226
  return EnumerableWrapper(result)
175
227
  return result
176
228
 
@@ -179,13 +231,12 @@ class wrap_enumerable:
179
231
  def __call__(self, *args, **kwargs):
180
232
  """
181
233
  Direct call support (when decorator is applied to a function, not a method).
182
-
234
+
183
235
  This is called when the wrapped method is invoked without going through
184
236
  the descriptor protocol (rare, but needed for some edge cases).
185
237
  """
186
238
  result = self.func(*args, **kwargs)
187
- # Only wrap if it looks like an IEnumerable (has GetEnumerator)
188
- if result is not None and hasattr(result, "GetEnumerator"):
239
+ if _needs_enumerable_wrap(result):
189
240
  return EnumerableWrapper(result)
190
241
  return result
191
242
 
@@ -1775,10 +1826,12 @@ class BaseOperations:
1775
1826
 
1776
1827
  def _ValidateParam(self, param: any, param_name: str = "parameter") -> None:
1777
1828
  """
1778
- Validate that a parameter is not None.
1829
+ Validate that a parameter is not None and not a stale LCM object.
1779
1830
 
1780
1831
  This method performs a null check on a required parameter. Use this for
1781
- any parameter that must be provided and non-None.
1832
+ any parameter that must be provided and non-None. When the parameter is
1833
+ a live LCM object reference, it is additionally rejected if it refers to
1834
+ a deleted/invalid object (``IsValidObject`` is False).
1782
1835
 
1783
1836
  Args:
1784
1837
  param: The parameter value to validate (any type).
@@ -1789,7 +1842,9 @@ class BaseOperations:
1789
1842
  None
1790
1843
 
1791
1844
  Raises:
1792
- Exception: If param is None.
1845
+ FP_NullParameterError: If param is None.
1846
+ FP_ParameterError: If param is an LCM object that has been deleted
1847
+ or otherwise invalidated (``IsValidObject`` is False).
1793
1848
 
1794
1849
  Example:
1795
1850
  >>> def SetGloss(self, sense, gloss):
@@ -1811,11 +1866,25 @@ class BaseOperations:
1811
1866
 
1812
1867
  Implementation Notes:
1813
1868
  - Simple if param is None check
1869
+ - Stale-reference guard: a cascade-deleted LCM object keeps a live
1870
+ .NET reference but its Cache/Services pointers are torn down, so
1871
+ touching any property raises a NullReferenceException from deep
1872
+ inside LCM. ICmObject exposes IsValidObject (False once deleted);
1873
+ mirrors LCM's own guard pattern (OverridesLing_Lex.cs:1500).
1874
+ - getattr(..., None) means non-LCM params (str/int/dict/etc.) have
1875
+ no IsValidObject attribute -> None -> skipped. Only an explicit
1876
+ False triggers the error, so a True value is never mistaken.
1814
1877
  - No side effects
1815
1878
  - Exception message includes parameter name
1816
1879
  """
1817
1880
  if param is None:
1818
1881
  raise FP_NullParameterError()
1882
+ # Reject stale LCM object references (cascade-deleted / invalidated).
1883
+ # Non-LCM params return None here and are left untouched.
1884
+ if getattr(param, "IsValidObject", None) is False:
1885
+ raise FP_ParameterError(
1886
+ f"{param_name} refers to a deleted or invalid LCM object"
1887
+ )
1819
1888
 
1820
1889
  def _ValidateParamNotEmpty(self, param: any, param_name: str = "parameter") -> None:
1821
1890
  """
@@ -82,7 +82,7 @@ class ConstChartOperations(BaseOperations):
82
82
  """
83
83
  Get all constituent charts in the project.
84
84
 
85
- This method returns an iterator over all IDsConstChart objects in the
85
+ This method returns an EnumerableWrapper (subscriptable, len()-able, lazily materialized) over all IDsConstChart objects in the
86
86
  project database, allowing iteration over all discourse analysis charts.
87
87
 
88
88
  Yields:
@@ -97,7 +97,7 @@ class ConstChartOperations(BaseOperations):
97
97
  Mark 1 Analysis (32 rows)
98
98
 
99
99
  Notes:
100
- - Returns an iterator for memory efficiency
100
+ - Returns an EnumerableWrapper (subscriptable, len()-able) for memory efficiency; the underlying LCM enumerator is only materialized into a list on first len()/index/iteration access
101
101
  - Charts are returned in database order
102
102
  - Each chart can contain multiple rows for analysis
103
103
  - Use GetName() to access the chart name
@@ -38,7 +38,7 @@ import logging
38
38
  import os
39
39
  import clr
40
40
 
41
- from .Shared.string_utils import normalize_ws_handle
41
+ from .Shared.string_utils import normalize_ws_handle, best_multistring_alternative
42
42
 
43
43
  clr.AddReference("System")
44
44
  import System
@@ -2407,42 +2407,10 @@ class FLExProject(object):
2407
2407
  self._constchartclausemarker_ops = ConstChartClauseMarkerOperations(self)
2408
2408
  return self._constchartclausemarker_ops
2409
2409
 
2410
- # Singular aliases for backward compatibility
2411
-
2412
- @property
2413
- def Agent(self):
2414
- """Alias for Agents (singular form for backward compatibility)."""
2415
- return self.Agents
2416
-
2417
- @property
2418
- def PossibilityList(self):
2419
- """Alias for PossibilityLists (singular form for backward compatibility)."""
2420
- return self.PossibilityLists
2421
-
2422
- @property
2423
- def WritingSystem(self):
2424
- """Alias for WritingSystems (singular form for backward compatibility)."""
2425
- return self.WritingSystems
2426
-
2427
- @property
2428
- def Overlay(self):
2429
- """Alias for Overlays (singular form for backward compatibility)."""
2430
- return self.Overlays
2431
-
2432
- @property
2433
- def Publication(self):
2434
- """Alias for Publications (singular form for backward compatibility)."""
2435
- return self.Publications
2436
-
2437
- @property
2438
- def TranslationType(self):
2439
- """Alias for TranslationTypes (singular form for backward compatibility)."""
2440
- return self.TranslationTypes
2441
-
2442
- @property
2443
- def Note(self):
2444
- """Alias for Notes (singular form for backward compatibility)."""
2445
- return self.Notes
2410
+ # Singular/plural aliases for backward compatibility are generated at
2411
+ # module scope from FLExProject._OP_NAMESPACE_ALIASES (see the end of
2412
+ # this module). Each generated alias emits a DeprecationWarning that
2413
+ # names the canonical accessor. See issue #200.
2446
2414
 
2447
2415
  def ImportLocalizedLists(self, language_code, progress=None):
2448
2416
  """Deprecated. Use ``project.LocalizedLists.Import(language_code)``."""
@@ -2469,21 +2437,6 @@ class FLExProject(object):
2469
2437
  progress=progress
2470
2438
  )
2471
2439
 
2472
- @property
2473
- def AnnotationDef(self):
2474
- """Alias for AnnotationDefs (singular form for backward compatibility)."""
2475
- return self.AnnotationDefs
2476
-
2477
- @property
2478
- def Check(self):
2479
- """Alias for Checks (singular form for backward compatibility)."""
2480
- return self.Checks
2481
-
2482
- @property
2483
- def CustomField(self):
2484
- """Alias for CustomFields (singular form for backward compatibility)."""
2485
- return self.CustomFields
2486
-
2487
2440
  # --- General ---
2488
2441
 
2489
2442
  def ProjectName(self):
@@ -2711,9 +2664,10 @@ class FLExProject(object):
2711
2664
  """
2712
2665
  import os
2713
2666
 
2714
- # Try to get LinkedFilesRootDir from project
2715
- if hasattr(self.project, "LinkedFilesRootDir") and self.project.LinkedFilesRootDir:
2716
- return self.project.LinkedFilesRootDir
2667
+ # Try to get LinkedFilesRootDir. It lives on ILangProject (self.lp), not
2668
+ # on the LcmCache (self.project) — see issue #226.
2669
+ if getattr(self.lp, "LinkedFilesRootDir", None):
2670
+ return self.lp.LinkedFilesRootDir
2717
2671
 
2718
2672
  # Fallback: construct default path from project folder
2719
2673
  if hasattr(self.project, "ProjectId") and hasattr(self.project.ProjectId, "ProjectFolder"):
@@ -3352,7 +3306,27 @@ class FLExProject(object):
3352
3306
  WSHandle = self.__WSHandle(languageTagOrHandle, None)
3353
3307
  return mua.get_String(WSHandle)
3354
3308
  else:
3355
- return ITsString(mua.BestAnalysisVernacularAlternative)
3309
+ # mua is a bare ITsMultiString (SIL.LCModel.Core.KernelInterfaces),
3310
+ # which has no BestAnalysisVernacularAlternative accessor (that
3311
+ # lives on IMultiAccessorBase). Reimplement the "best available
3312
+ # alternative" priority walk on top of get_String() instead.
3313
+ # See issue #224.
3314
+ analWsHandles = [int(ws.Handle) for ws in self.lp.CurrentAnalysisWritingSystems]
3315
+ vernWsHandles = [int(ws.Handle) for ws in self.lp.CurrentVernacularWritingSystems]
3316
+ best = best_multistring_alternative(
3317
+ mua,
3318
+ self.project.DefaultAnalWs,
3319
+ self.project.DefaultVernWs,
3320
+ analWsHandles,
3321
+ vernWsHandles,
3322
+ )
3323
+ if best is not None:
3324
+ return best
3325
+ # Total miss across every configured WS: still return a
3326
+ # (possibly empty) ITsString so this branch's contract
3327
+ # matches the sibling branches above/below, which always
3328
+ # return ITsString.
3329
+ return ITsString(mua.get_String(self.project.DefaultAnalWs))
3356
3330
 
3357
3331
  elif fieldType == CellarPropertyType.Integer:
3358
3332
  return self.project.DomainDataByFlid.get_IntProp(hvo, fieldID)
@@ -4421,3 +4395,20 @@ class FLExProject(object):
4421
4395
  yield name, "\n".join(content)
4422
4396
  else:
4423
4397
  yield "\n".join(content)
4398
+
4399
+
4400
+ # ============================================================================
4401
+ # Operation-namespace naming aliases (issue #200)
4402
+ # ============================================================================
4403
+ # Deprecated singular/plural aliases for the operation-namespace accessors are
4404
+ # generated from a single table in the SIL-free ._op_aliases module and
4405
+ # attached here. Each alias forwards to the canonical accessor and emits a
4406
+ # DeprecationWarning. Keeping the table and generator SIL-free lets them be
4407
+ # unit-tested without FieldWorks (tests/test_flexproject_aliases.py).
4408
+ from ._op_aliases import ( # noqa: E402
4409
+ OP_NAMESPACE_ALIASES as _OP_NAMESPACE_ALIASES,
4410
+ install_op_namespace_aliases as _install_op_namespace_aliases,
4411
+ )
4412
+
4413
+ FLExProject._OP_NAMESPACE_ALIASES = _OP_NAMESPACE_ALIASES
4414
+ _install_op_namespace_aliases(FLExProject)
@@ -262,6 +262,7 @@ class MorphRuleOperations(BaseOperations):
262
262
  return AffixTemplateCollection(wrapped)
263
263
  return AffixTemplateCollection()
264
264
 
265
+ @wrap_enumerable
265
266
  @OperationsMethod
266
267
  def GetAllAdhocCoProhibitions(self):
267
268
  """
@@ -736,7 +737,7 @@ class MorphRuleOperations(BaseOperations):
736
737
  # ========== DUPLICATION ==========
737
738
 
738
739
  @OperationsMethod
739
- def Duplicate(self, item_or_hvo, insert_after=True):
740
+ def Duplicate(self, item_or_hvo, insert_after=True, deep=True):
740
741
  """
741
742
  Duplicate a morphological rule or template, creating a copy with a new GUID.
742
743
 
@@ -748,10 +749,15 @@ class MorphRuleOperations(BaseOperations):
748
749
  item_or_hvo: The rule object or HVO to duplicate.
749
750
  insert_after (bool): If True (default), insert after the source.
750
751
  If False, insert at end of collection.
751
- deep (bool): If True, copy reference sequences (slot assignments
752
- for affix templates). If False (default), only copy simple
752
+ deep (bool): If True (default), copy reference sequences (slot
753
+ assignments for affix templates). If False, only copy simple
753
754
  properties.
754
755
 
756
+ Note: this default (True) matches LexEntry/Text (deep by
757
+ default); it differs from Media/Wordform (deep=False by
758
+ default). See issue #203 -- the split is deliberate per
759
+ object family, not an inconsistency to "fix" elsewhere.
760
+
755
761
  Returns:
756
762
  The newly created duplicate with a new GUID.
757
763
 
@@ -25,7 +25,6 @@ from SIL.LCModel import (
25
25
  IMoAffixAllomorphFactory,
26
26
  ILexEntry,
27
27
  IPhEnvironment,
28
- MoMorphTypeTags,
29
28
  )
30
29
  from SIL.LCModel.Core.KernelInterfaces import ITsString
31
30
  from SIL.LCModel.Core.Text import TsStringUtils
@@ -38,6 +37,10 @@ from ..FLExProject import (
38
37
  # Import string utilities
39
38
  from ..Shared.string_utils import normalize_text
40
39
 
40
+ # Import shared morph-type resolution utilities (single source of truth
41
+ # shared with LexEntryOperations.Create -- see issue #213/#214)
42
+ from ..Shared.morph_type_utils import find_morph_type, is_stem_morph_type, morph_type_not_found_error
43
+
41
44
  # Import wrapper classes
42
45
  from .allomorph import Allomorph
43
46
  from .allomorph_collection import AllomorphCollection
@@ -258,27 +261,13 @@ class AllomorphOperations(BaseOperations):
258
261
  wsHandle = self.__WSHandle(wsHandle)
259
262
 
260
263
  # Accept morph type as a string name (with or without display markers).
264
+ # Uses the SAME resolver as LexEntryOperations.Create (see issue #213)
265
+ # so '=enclitic', 'proclitic=', '-suffix', etc. all resolve identically
266
+ # regardless of which operations class is used.
261
267
  if isinstance(morphType, str):
262
- bare = morphType.strip('-=~<>')
263
- morph_types = self.project.lp.LexDbOA.MorphTypesOA
264
- from ..Shared.string_utils import normalize_match_key, best_analysis_text
265
- target = normalize_match_key(bare, casefold=True)
266
- def _find(possibilities):
267
- for mt in possibilities:
268
- mt_name = best_analysis_text(mt.Name)
269
- if mt_name and normalize_match_key(mt_name, casefold=True) == target:
270
- return mt
271
- if mt.SubPossibilitiesOS.Count > 0:
272
- found = _find(mt.SubPossibilitiesOS)
273
- if found:
274
- return found
275
- return None
276
- resolved = _find(morph_types.PossibilitiesOS) if morph_types else None
268
+ resolved = find_morph_type(self.project, morphType)
277
269
  if resolved is None:
278
- raise FP_ParameterError(
279
- f"Morph type '{morphType}' not found. "
280
- "Use project.LexEntry.GetAvailableMorphTypes() to see valid names."
281
- )
270
+ raise FP_ParameterError(morph_type_not_found_error(morphType))
282
271
  morphType = resolved
283
272
 
284
273
  # If no morphType provided, inherit from entry's LexemeFormOA (FLEx behavior)
@@ -1127,32 +1116,29 @@ class AllomorphOperations(BaseOperations):
1127
1116
  Determine if a morph type should use MoStemAllomorph or MoAffixAllomorph.
1128
1117
 
1129
1118
  Args:
1130
- morph_type: IMoMorphType object
1119
+ morph_type: IMoMorphType object, or a string morph-type name.
1131
1120
 
1132
1121
  Returns:
1133
1122
  bool: True if stem type (uses MoStemAllomorph), False if affix type
1134
1123
 
1124
+ Raises:
1125
+ FP_ParameterError: If morph_type is a string that does not
1126
+ resolve to a known morph type.
1127
+
1135
1128
  Notes:
1136
- Based on FLEx logic - matches LexEntryOperations.__IsStemType
1129
+ Delegates to Shared.morph_type_utils.is_stem_morph_type -- the
1130
+ same classification used by LexEntryOperations.__IsStemType.
1131
+ Accepts a string here (in addition to an IMoMorphType object)
1132
+ so that callers passing a raw name never hit an AttributeError
1133
+ deep in this helper (see issue #213).
1137
1134
  """
1138
- if morph_type is None:
1139
- return True # Default to stem
1140
-
1141
- # Check GUID against known stem types
1142
- stem_guids = {
1143
- MoMorphTypeTags.kguidMorphStem,
1144
- MoMorphTypeTags.kguidMorphRoot,
1145
- MoMorphTypeTags.kguidMorphBoundRoot,
1146
- MoMorphTypeTags.kguidMorphBoundStem,
1147
- MoMorphTypeTags.kguidMorphClitic,
1148
- MoMorphTypeTags.kguidMorphEnclitic,
1149
- MoMorphTypeTags.kguidMorphProclitic,
1150
- MoMorphTypeTags.kguidMorphParticle,
1151
- MoMorphTypeTags.kguidMorphPhrase,
1152
- MoMorphTypeTags.kguidMorphDiscontiguousPhrase,
1153
- }
1154
-
1155
- return morph_type.Guid in stem_guids
1135
+ if isinstance(morph_type, str):
1136
+ resolved = find_morph_type(self.project, morph_type)
1137
+ if resolved is None:
1138
+ raise FP_ParameterError(morph_type_not_found_error(morph_type))
1139
+ morph_type = resolved
1140
+
1141
+ return is_stem_morph_type(morph_type)
1156
1142
 
1157
1143
  def __WSHandle(self, wsHandle):
1158
1144
  """
@@ -29,7 +29,6 @@ from SIL.LCModel import (
29
29
  IMoStemAllomorphFactory,
30
30
  IMoAffixAllomorphFactory,
31
31
  IMoForm,
32
- MoMorphTypeTags,
33
32
  ILexEntryRef,
34
33
  ILexEntryRefFactory,
35
34
  LexEntryRefTags,
@@ -48,6 +47,15 @@ from ..FLExProject import (
48
47
  # Import string utilities
49
48
  from ..Shared.string_utils import normalize_text, normalize_match_key, best_analysis_text, best_vernacular_text
50
49
 
50
+ # Import shared morph-type resolution utilities (single source of truth
51
+ # for name lookup, display-marker normalization, and stem/affix
52
+ # classification -- also used by AllomorphOperations)
53
+ from ..Shared.morph_type_utils import (
54
+ find_morph_type,
55
+ is_stem_morph_type,
56
+ morph_type_not_found_error,
57
+ )
58
+
51
59
 
52
60
  class LexEntryOperations(BaseOperations):
53
61
  """
@@ -101,7 +109,7 @@ class LexEntryOperations(BaseOperations):
101
109
  """
102
110
  Get all lexical entries in the project.
103
111
 
104
- This method returns an iterator over all ILexEntry objects in the
112
+ This method returns an EnumerableWrapper (subscriptable, len()-able, lazily materialized) over all ILexEntry objects in the
105
113
  project database, allowing iteration over the complete lexicon.
106
114
 
107
115
  Yields:
@@ -117,7 +125,7 @@ class LexEntryOperations(BaseOperations):
117
125
  house (4 senses)
118
126
 
119
127
  Notes:
120
- - Returns an iterator for memory efficiency with large lexicons
128
+ - Returns an EnumerableWrapper (subscriptable, len()-able) for memory efficiency with large lexicons; the underlying LCM enumerator is only materialized into a list on first len()/index/iteration access
121
129
  - Entries are returned in database order (not alphabetical)
122
130
  - Use GetHeadword() to access the display form
123
131
  - For sorted entries, use FLExProject.LexiconAllEntriesSorted()
@@ -201,9 +209,7 @@ class LexEntryOperations(BaseOperations):
201
209
  # Find the morph type
202
210
  morph_type = self.__FindMorphType(morph_type_name)
203
211
  if morph_type is None:
204
- raise FP_ParameterError(
205
- f"Morph type '{morph_type_name}' not found. " f"Use one of: stem, root, prefix, suffix, infix, etc."
206
- )
212
+ raise FP_ParameterError(morph_type_not_found_error(morph_type_name))
207
213
 
208
214
  with self._TransactionCM(f"Create entry '{lexeme_form}'"):
209
215
  # Create the new entry using the factory
@@ -1520,7 +1526,7 @@ class LexEntryOperations(BaseOperations):
1520
1526
  if isinstance(morph_type_or_name, str):
1521
1527
  morph_type = self.__FindMorphType(morph_type_or_name)
1522
1528
  if morph_type is None:
1523
- raise FP_ParameterError(f"Morph type '{morph_type_or_name}' not found")
1529
+ raise FP_ParameterError(morph_type_not_found_error(morph_type_or_name))
1524
1530
  else:
1525
1531
  morph_type = morph_type_or_name
1526
1532
 
@@ -3129,25 +3135,11 @@ class LexEntryOperations(BaseOperations):
3129
3135
  Based on FLEx logic in MorphTypeAtomicLauncher.cs
3130
3136
  Stem types include: stem, root, bound root/stem, clitics, particles, phrases
3131
3137
  Affix types include: prefix, suffix, infix, circumfix, etc.
3138
+ Delegates to Shared.morph_type_utils.is_stem_morph_type so this
3139
+ classification lives in exactly one place (also used by
3140
+ AllomorphOperations).
3132
3141
  """
3133
- if morph_type is None:
3134
- return True # Default to stem
3135
-
3136
- # Check GUID against known stem types (from MoMorphTypeTags)
3137
- stem_guids = {
3138
- MoMorphTypeTags.kguidMorphStem,
3139
- MoMorphTypeTags.kguidMorphRoot,
3140
- MoMorphTypeTags.kguidMorphBoundRoot,
3141
- MoMorphTypeTags.kguidMorphBoundStem,
3142
- MoMorphTypeTags.kguidMorphClitic,
3143
- MoMorphTypeTags.kguidMorphEnclitic,
3144
- MoMorphTypeTags.kguidMorphProclitic,
3145
- MoMorphTypeTags.kguidMorphParticle,
3146
- MoMorphTypeTags.kguidMorphPhrase,
3147
- MoMorphTypeTags.kguidMorphDiscontiguousPhrase,
3148
- }
3149
-
3150
- return morph_type.Guid in stem_guids
3142
+ return is_stem_morph_type(morph_type)
3151
3143
 
3152
3144
  def __WSHandleAnalysis(self, wsHandle):
3153
3145
  """
@@ -3176,28 +3168,10 @@ class LexEntryOperations(BaseOperations):
3176
3168
 
3177
3169
  Returns:
3178
3170
  IMoMorphType or None: The morph type object if found, None otherwise
3179
- """
3180
- # Strip display markers (prefix '-', '=', '~'; postfix '-', '=', '~', '<', '>')
3181
- # that FLEx shows in the UI but that are NOT part of the canonical IMoMorphType.Name.
3182
- bare = name.strip('-=~<>')
3183
- target = normalize_match_key(bare, casefold=True)
3184
- wsHandle = self.project.project.DefaultAnalWs
3185
-
3186
- morph_types = self.project.lp.LexDbOA.MorphTypesOA
3187
- if morph_types is None:
3188
- return None
3189
3171
 
3190
- # Search through all morph types (including subcategories)
3191
- def search_morph_types(possibilities):
3192
- for mt in possibilities:
3193
- mt_name = best_analysis_text(mt.Name)
3194
- if mt_name and normalize_match_key(mt_name, casefold=True) == target:
3195
- return mt
3196
- # Search subcategories
3197
- if mt.SubPossibilitiesOS.Count > 0:
3198
- found = search_morph_types(mt.SubPossibilitiesOS)
3199
- if found:
3200
- return found
3201
- return None
3202
-
3203
- return search_morph_types(morph_types.PossibilitiesOS)
3172
+ Notes:
3173
+ Delegates to Shared.morph_type_utils.find_morph_type so this is
3174
+ the single lookup used by both LexEntryOperations and
3175
+ AllomorphOperations (see issue #213/#214).
3176
+ """
3177
+ return find_morph_type(self.project, name)