deriva-cli 0.8.2__tar.gz → 0.8.4__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 (204) hide show
  1. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/PKG-INFO +3 -3
  2. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/manager.py +29 -0
  3. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/views.py +56 -23
  4. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/xml_export.py +32 -8
  5. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/database/__init__.py +2 -0
  6. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/database/manager.py +19 -1
  7. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/grafeo/__init__.py +8 -0
  8. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/grafeo/manager.py +51 -0
  9. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/graph/manager.py +5 -3
  10. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/graph/metamodel.py +43 -1
  11. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/graph/models.py +39 -0
  12. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/llm/manager.py +81 -7
  13. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/llm/model_registry.py +20 -3
  14. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/llm/models.py +7 -3
  15. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/llm/retry.py +5 -1
  16. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/nlp/translate.py +1 -2
  17. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/repository/manager.py +160 -32
  18. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/repository/models.py +2 -0
  19. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/languages/csharp.py +2 -2
  20. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/languages/javascript.py +25 -14
  21. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/languages/python.py +5 -3
  22. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/manager.py +1 -1
  23. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/cli/cli.py +85 -5
  24. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/cli/commands/config.py +53 -2
  25. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/cli/commands/graph.py +1 -0
  26. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/cli/commands/repo.py +52 -5
  27. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/cli/commands/run.py +7 -2
  28. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/file_utils.py +28 -1
  29. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/settings.py +8 -1
  30. deriva_cli-0.8.4/deriva/common/urls.py +15 -0
  31. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/analysis/semantic_matching.py +16 -4
  32. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/base.py +14 -1
  33. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/component_services.py +12 -2
  34. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/detail_level.py +5 -3
  35. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/element_base.py +22 -7
  36. deriva_cli-0.8.4/deriva/modules/derivation/levels.py +224 -0
  37. deriva_cli-0.8.4/deriva/modules/derivation/quality.py +107 -0
  38. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/refine/__init__.py +2 -0
  39. deriva_cli-0.8.4/deriva/modules/derivation/refine/abstraction_levels.py +261 -0
  40. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/refine/joint_consistency.py +28 -16
  41. deriva_cli-0.8.4/deriva/modules/extraction/build_modules.py +254 -0
  42. deriva_cli-0.8.4/deriva/modules/extraction/coverage.py +126 -0
  43. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/data_domains.py +5 -1
  44. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/data_entities.py +22 -6
  45. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/deployable_units.py +8 -2
  46. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/deployments.py +16 -2
  47. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/directory_classification.py +4 -1
  48. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/edges.py +2 -1
  49. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/step_tags.py +4 -0
  50. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/__init__.py +8 -2
  51. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/config.py +75 -21
  52. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/derivation.py +55 -0
  53. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/export_bundle.py +5 -0
  54. deriva_cli-0.8.4/deriva/services/export_levels.py +117 -0
  55. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/extraction.py +253 -19
  56. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/model_configs.py +70 -2
  57. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/ontology.py +30 -16
  58. deriva_cli-0.8.4/deriva/services/quality.py +47 -0
  59. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/session.py +213 -17
  60. deriva_cli-0.8.4/deriva/services/storage.py +92 -0
  61. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/pyproject.toml +3 -3
  62. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/pyproject.toml.orig +3 -3
  63. deriva_cli-0.8.2/deriva/services/export_levels.py +0 -71
  64. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/LICENSE +0 -0
  65. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/README.md +0 -0
  66. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/__init__.py +0 -0
  67. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/documentation/Diagram.html +0 -0
  68. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/documentation/View.html +0 -0
  69. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/documentation/model.html +0 -0
  70. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/examples/archimate3_Diagram.xsd +0 -0
  71. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/examples/archimate3_Model.xsd +0 -0
  72. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/examples/archimate3_View.xsd +0 -0
  73. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/schemas/archimate3_Diagram.xsd +0 -0
  74. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/schemas/archimate3_Model.xsd +0 -0
  75. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/schemas/archimate3_View.xsd +0 -0
  76. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-1.xml +0 -0
  77. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-10.xml +0 -0
  78. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-2.xml +0 -0
  79. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-3.xml +0 -0
  80. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-4.xml +0 -0
  81. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-5.xml +0 -0
  82. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-7.xml +0 -0
  83. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-8.xml +0 -0
  84. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-9.xml +0 -0
  85. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/models.py +0 -0
  86. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/archimate/validation.py +0 -0
  87. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/database/README.md +0 -0
  88. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/database/data/derivation_config.json +0 -0
  89. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/database/data/derivation_patterns.json +0 -0
  90. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/database/data/extraction_config.json +0 -0
  91. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/database/data/file_types.json +0 -0
  92. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/database/data/function_models/pcf-8.0-cross-industry.csv +0 -0
  93. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/database/data/system_settings.json +0 -0
  94. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/database/db_tool.py +0 -0
  95. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/database/scripts/7_extraction_params.sql +0 -0
  96. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/database/scripts/schema.sql +0 -0
  97. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/graph/README.md +0 -0
  98. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/graph/__init__.py +0 -0
  99. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/graph/cache.py +0 -0
  100. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/llm/README.md +0 -0
  101. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/llm/__init__.py +0 -0
  102. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/llm/cache.py +0 -0
  103. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/llm/rate_limiter.py +0 -0
  104. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/llm/schemas.py +0 -0
  105. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/nlp/__init__.py +0 -0
  106. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/nlp/extract.py +0 -0
  107. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/nlp/language.py +0 -0
  108. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/nlp/manager.py +0 -0
  109. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/nlp/models.py +0 -0
  110. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/nlp/phrases.py +0 -0
  111. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/nlp/settings.py +0 -0
  112. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/nlp/text.py +0 -0
  113. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/repository/README.md +0 -0
  114. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/repository/__init__.py +0 -0
  115. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/__init__.py +0 -0
  116. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/cdk.py +0 -0
  117. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/languages/__init__.py +0 -0
  118. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/languages/base.py +0 -0
  119. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/languages/bms.py +0 -0
  120. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/languages/cobol.py +0 -0
  121. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/languages/java.py +0 -0
  122. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/languages/jcl.py +0 -0
  123. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/languages/sql.py +0 -0
  124. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/languages/structural.py +0 -0
  125. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/adapters/treesitter/models.py +0 -0
  126. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/cli/README.md +0 -0
  127. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/cli/__init__.py +0 -0
  128. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/cli/commands/__init__.py +0 -0
  129. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/cli/progress.py +0 -0
  130. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/__init__.py +0 -0
  131. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/cache_utils.py +0 -0
  132. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/chunking.py +0 -0
  133. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/document_reader.py +0 -0
  134. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/exceptions.py +0 -0
  135. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/json_utils.py +0 -0
  136. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/llm_utils.py +0 -0
  137. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/logging.py +0 -0
  138. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/naming.py +0 -0
  139. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/ocel.py +0 -0
  140. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/paths.py +0 -0
  141. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/schema_utils.py +0 -0
  142. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/time_utils.py +0 -0
  143. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/timing.py +0 -0
  144. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/common/types.py +0 -0
  145. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/__init__.py +0 -0
  146. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/analysis/__init__.py +0 -0
  147. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/analysis/consistency.py +0 -0
  148. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/analysis/conventions.py +0 -0
  149. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/analysis/cross_repo_analysis.py +0 -0
  150. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/analysis/deviation.py +0 -0
  151. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/analysis/fit_analysis.py +0 -0
  152. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/analysis/model_quality.py +0 -0
  153. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/analysis/run_consistency.py +0 -0
  154. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/analysis/stability_analysis.py +0 -0
  155. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/analysis/types.py +0 -0
  156. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/__init__.py +0 -0
  157. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/application_component.py +0 -0
  158. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/application_interface.py +0 -0
  159. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/application_service.py +0 -0
  160. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/business_actor.py +0 -0
  161. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/business_event.py +0 -0
  162. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/business_function.py +0 -0
  163. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/business_object.py +0 -0
  164. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/business_process.py +0 -0
  165. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/data_object.py +0 -0
  166. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/device.py +0 -0
  167. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/function_model.py +0 -0
  168. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/node.py +0 -0
  169. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/prep.py +0 -0
  170. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/refine/base.py +0 -0
  171. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/refine/cross_layer.py +0 -0
  172. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/refine/duplicate_elements.py +0 -0
  173. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/refine/duplicate_relationships.py +0 -0
  174. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/refine/graph_relationships.py +0 -0
  175. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/refine/joint.py +0 -0
  176. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/refine/normalization.py +0 -0
  177. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/refine/orphan_elements.py +0 -0
  178. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/refine/structural_consistency.py +0 -0
  179. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/system_software.py +0 -0
  180. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/derivation/technology_service.py +0 -0
  181. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/__init__.py +0 -0
  182. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/base.py +0 -0
  183. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/business_concept.py +0 -0
  184. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/classification.py +0 -0
  185. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/concept_candidates.py +0 -0
  186. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/cooperation.py +0 -0
  187. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/directory.py +0 -0
  188. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/external_dependency.py +0 -0
  189. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/file.py +0 -0
  190. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/lifecycles.py +0 -0
  191. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/method.py +0 -0
  192. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/repository.py +0 -0
  193. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/roles.py +0 -0
  194. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/technologies.py +0 -0
  195. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/technology_candidates.py +0 -0
  196. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/test.py +0 -0
  197. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/modules/extraction/type_definition.py +0 -0
  198. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/about.py +0 -0
  199. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/config_models.py +0 -0
  200. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/llm_log.py +0 -0
  201. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/overfit.py +0 -0
  202. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/pipeline.py +0 -0
  203. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/deriva/services/trace.py +0 -0
  204. {deriva_cli-0.8.2 → deriva_cli-0.8.4}/packages/README-deriva-cli.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deriva-cli
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: Deriva is a research project aimed at enabling digital architects by automating the derivation of enterprise architecture models. Deriva uses dual knowledge graphs, heuristics and LLM's.
5
5
  Keywords: archimate,enterprise-architecture,code-analysis,llm,grafeo,fastapi
6
6
  Author: S. T. Grond
@@ -38,8 +38,8 @@ Requires-Dist: spacy==3.8.16
38
38
  Requires-Dist: ctranslate2==4.8.3
39
39
  Requires-Dist: sentencepiece==0.2.2
40
40
  Requires-Dist: solvor>=0.6.3
41
- Requires-Dist: pydantic-ai-slim[openai,anthropic]>=2.55
42
- Requires-Dist: httpx2>=2.13
41
+ Requires-Dist: pydantic-ai-slim[openai,anthropic]>=2.57
42
+ Requires-Dist: httpx2>=2.14
43
43
  Requires-Dist: lmdb>=1.6
44
44
  Requires-Dist: typer>=0.27
45
45
  Requires-Dist: tree-sitter-typescript==0.23.2
@@ -577,6 +577,35 @@ class ArchimateManager:
577
577
  key=lambda e: str(e["identifier"]),
578
578
  )
579
579
 
580
+ def update_element_properties(self, updates: dict[str, dict[str, Any]]) -> int:
581
+ """Merge keys into the properties of elements (identifier -> keys), leaving their other keys; returns how many changed."""
582
+ if self.db is None:
583
+ raise RuntimeError("Not connected to grafeo. Call connect() first.")
584
+ changed = 0
585
+ for identifier, keys in sorted(updates.items()):
586
+ rows = self.db.execute(f"MATCH (e:`{self.namespace}` {{identifier: $identifier}}) RETURN e.properties AS properties", {"identifier": identifier})
587
+ if not rows:
588
+ continue
589
+ merged = {**(rows[0].get("properties") or {}), **keys}
590
+ self.db.execute_write(f"MATCH (e:`{self.namespace}` {{identifier: $identifier}}) SET e.properties = $properties", {"identifier": identifier, "properties": merged})
591
+ changed += 1
592
+ return changed
593
+
594
+ def update_relationship_properties(self, updates: dict[str, dict[str, Any]]) -> int:
595
+ """Merge keys into the properties of relationships (identifier -> keys), in place; returns how many changed."""
596
+ if self.db is None:
597
+ raise RuntimeError("Not connected to grafeo. Call connect() first.")
598
+ changed = 0
599
+ for identifier, keys in sorted(updates.items()):
600
+ match = f"MATCH (:`{self.namespace}`)-[r {{identifier: $identifier}}]->(:`{self.namespace}`)"
601
+ rows = self.db.execute(f"{match} RETURN r.properties AS properties", {"identifier": identifier})
602
+ if not rows:
603
+ continue
604
+ merged = {**(rows[0].get("properties") or {}), **keys}
605
+ self.db.execute_write(f"{match} SET r.properties = $properties", {"identifier": identifier, "properties": merged})
606
+ changed += 1
607
+ return changed
608
+
580
609
  def delete_relationship(self, identifier: str) -> bool:
581
610
  """Delete a relationship by identifier.
582
611
 
@@ -1,7 +1,8 @@
1
1
  """Generated views (pure): ArchiMate viewpoints per level with a deterministic band layout.
2
2
 
3
- Each viewpoint (ArchiMate 3.2 Specification, chapter 13) shows the architect elements of its types; a detail
4
- variant adds the detail elements (property ``level`` = detail) nested under those types. The layout puts the
3
+ Each viewpoint (ArchiMate 3.2 Specification, chapter 13) shows the elements of its types on its level (enterprise or
4
+ solution, from the element's ``levels``) and the relationships on that level; a cross-level link is in no view. A
5
+ detail variant shows the solution elements of its types with their detail elements (property ``level`` = detail). The layout puts the
5
6
  layers in horizontal bands (business, application, technology) and the elements of a band in a grid, ordered by
6
7
  type and name, each detail element right after its parent, so the same model gives the same view in every run.
7
8
  """
@@ -12,23 +13,32 @@ from dataclasses import dataclass, field
12
13
 
13
14
  from .models import Element, Relationship, layer_of
14
15
 
15
- # (view name, viewpoint, element types, with detail elements)
16
- VIEWPOINTS: tuple[tuple[str, str, frozenset[str] | None, bool], ...] = (
17
- ("Layered", "Layered", None, False),
18
- ("Application Cooperation", "Application Cooperation", frozenset({"ApplicationComponent", "ApplicationInterface", "ApplicationService"}), False),
19
- ("Information Structure", "Information Structure", frozenset({"BusinessObject", "DataObject"}), False),
20
- ("Information Structure (detail)", "Information Structure", frozenset({"BusinessObject", "DataObject"}), True),
21
- ("Implementation and Deployment", "Implementation and Deployment", frozenset({"ApplicationComponent", "Node", "Device", "SystemSoftware", "TechnologyService"}), False),
16
+ ENTERPRISE, SOLUTION, DETAIL = "enterprise", "solution", "detail"
17
+ DEPLOYMENT = frozenset({"ApplicationComponent", "Node", "Device", "SystemSoftware", "TechnologyService"})
18
+ # (view name, viewpoint, element types, level), in export order
19
+ VIEWPOINTS: tuple[tuple[str, str, frozenset[str] | None, str], ...] = (
20
+ ("Layered (enterprise)", "Layered", None, ENTERPRISE),
21
+ ("Application Cooperation (enterprise)", "Application Cooperation", frozenset({"ApplicationComponent", "ApplicationInterface", "ApplicationService"}), ENTERPRISE),
22
+ ("Information Structure (enterprise)", "Information Structure", frozenset({"BusinessObject", "DataObject"}), ENTERPRISE),
23
+ ("Implementation and Deployment (enterprise)", "Implementation and Deployment", DEPLOYMENT, ENTERPRISE),
22
24
  (
23
- "Implementation and Deployment (detail)",
24
- "Implementation and Deployment",
25
- frozenset({"ApplicationComponent", "Node", "Device", "SystemSoftware", "TechnologyService", "Artifact"}),
26
- True,
25
+ "Business (enterprise)",
26
+ "Business Process Cooperation",
27
+ frozenset({"BusinessActor", "BusinessProcess", "BusinessFunction", "BusinessEvent", "ApplicationService"}),
28
+ ENTERPRISE,
27
29
  ),
28
- ("Business", "Business Process Cooperation", frozenset({"BusinessActor", "BusinessProcess", "BusinessFunction", "BusinessEvent", "ApplicationService"}), False),
30
+ ("Application Structure (solution)", "Application Structure", frozenset({"ApplicationComponent"}), SOLUTION),
31
+ ("Application Cooperation (solution)", "Application Cooperation", frozenset({"ApplicationComponent", "ApplicationInterface"}), SOLUTION),
32
+ ("Information Structure (solution)", "Information Structure", frozenset({"DataObject"}), SOLUTION),
33
+ ("Implementation and Deployment (solution)", "Implementation and Deployment", DEPLOYMENT, SOLUTION),
34
+ ("Information Structure (detail)", "Information Structure", frozenset({"DataObject"}), DETAIL),
35
+ ("Implementation and Deployment (detail)", "Implementation and Deployment", DEPLOYMENT | {"Artifact"}, DETAIL),
36
+ # The interfaces with their members (endpoints, pages, jobs); components are left out, their members are above
37
+ ("Application Interfaces (detail)", "Application Cooperation", frozenset({"ApplicationInterface"}), DETAIL),
29
38
  )
39
+
30
40
  # A view needs an element of its anchor types (the Business view: a business element, not application services alone)
31
- ANCHORS = {"Business": frozenset({"BusinessActor", "BusinessProcess", "BusinessFunction", "BusinessEvent"})}
41
+ ANCHORS = {"Business (enterprise)": frozenset({"BusinessActor", "BusinessProcess", "BusinessFunction", "BusinessEvent"})}
32
42
  BANDS = ("Business", "Application", "Technology", "Other")
33
43
  TYPE_ORDER = (
34
44
  "BusinessActor", "BusinessFunction", "BusinessProcess", "BusinessEvent", "BusinessObject",
@@ -61,7 +71,7 @@ class View:
61
71
  identifier: str
62
72
  name: str
63
73
  viewpoint: str
64
- level: str = "architect" # detail: the view shows the detail elements too
74
+ level: str = ENTERPRISE # enterprise, solution or detail (a detail view shows the detail elements too)
65
75
  nodes: list[ViewNode] = field(default_factory=list)
66
76
  connections: list[ViewConnection] = field(default_factory=list)
67
77
 
@@ -70,6 +80,23 @@ def _level(e: Element) -> str:
70
80
  return str((e.properties or {}).get("level") or "architect")
71
81
 
72
82
 
83
+ def _levels(properties: dict | None) -> frozenset[str]:
84
+ """The levels of an element or relationship: ``level`` detail, else ``levels`` (empty: a cross-level link), else the
85
+ enterprise level (a model from before the levels)."""
86
+ properties = properties or {}
87
+ if properties.get("level") == DETAIL:
88
+ return frozenset({DETAIL})
89
+ levels = properties.get("levels")
90
+ if isinstance(levels, list):
91
+ return frozenset(str(v) for v in levels)
92
+ return frozenset() if properties.get("cross_level") else frozenset({ENTERPRISE})
93
+
94
+
95
+ def _on(level: str, levels: frozenset[str]) -> bool:
96
+ """Whether something on ``levels`` belongs in a view of ``level`` (a detail view: the solution level and the detail)."""
97
+ return bool(levels & ({SOLUTION, DETAIL} if level == DETAIL else {level}))
98
+
99
+
73
100
  def _order(e: Element) -> tuple[int, str, str]:
74
101
  return (TYPE_ORDER.index(e.element_type) if e.element_type in TYPE_ORDER else len(TYPE_ORDER), e.name.casefold(), e.identifier)
75
102
 
@@ -102,19 +129,25 @@ def _layout(view: View, shown: list[Element]) -> None:
102
129
  y += BAND_GAP
103
130
 
104
131
 
105
- def build_views(elements: list[Element], relationships: list[Relationship]) -> list[View]:
106
- """The views of the model, one per viewpoint that has elements, in VIEWPOINTS order."""
132
+ def build_views(elements: list[Element], relationships: list[Relationship], relationship_levels: dict[str, frozenset[str]] | None = None) -> list[View]:
133
+ """The views of the model, one per viewpoint that has elements on its level, in VIEWPOINTS order.
134
+
135
+ Args:
136
+ relationship_levels: Relationship id -> its levels, where the relationship's own properties do not say (the
137
+ export's detail relationships); else from the properties
138
+ """
107
139
  views: list[View] = []
108
- for number, (name, viewpoint, types, with_detail) in enumerate(VIEWPOINTS, 1):
109
- shown = [e for e in elements if (types is None or e.element_type in types) and (with_detail or _level(e) != "detail")]
140
+ for number, (name, viewpoint, types, level) in enumerate(VIEWPOINTS, 1):
141
+ shown = [e for e in elements if (types is None or e.element_type in types) and _on(level, _levels(e.properties))]
110
142
  anchors = ANCHORS.get(name)
111
- if not shown or (anchors and not any(e.element_type in anchors for e in shown)) or (with_detail and not any(_level(e) == "detail" for e in shown)):
143
+ if not shown or (anchors and not any(e.element_type in anchors for e in shown)) or (level == DETAIL and not any(_level(e) == DETAIL for e in shown)):
112
144
  continue
113
- view = View(identifier=f"view-{number}", name=name, viewpoint=viewpoint, level="detail" if with_detail else "architect")
145
+ view = View(identifier=f"view-{number}", name=name, viewpoint=viewpoint, level=level)
114
146
  _layout(view, shown)
115
147
  node_of = {n.element: n.identifier for n in view.nodes}
116
148
  for r in sorted(relationships, key=lambda r: r.identifier):
117
- if r.source in node_of and r.target in node_of:
149
+ on = (relationship_levels or {}).get(r.identifier) or _levels(r.properties)
150
+ if r.source in node_of and r.target in node_of and _on(level, on):
118
151
  connection = ViewConnection(identifier=f"{view.identifier}-c-{r.identifier}", relationship=r.identifier, source=node_of[r.source], target=node_of[r.target])
119
152
  view.connections.append(connection)
120
153
  views.append(view)
@@ -51,6 +51,7 @@ class ArchiMateXMLExporter:
51
51
  model_id: str = "model-001",
52
52
  properties: Mapping[str, Mapping[str, str]] | None = None,
53
53
  views: Sequence[View] | None = None,
54
+ relationship_properties: Mapping[str, Mapping[str, str]] | None = None,
54
55
  ) -> None:
55
56
  """
56
57
  Export ArchiMate model to XML file.
@@ -63,6 +64,7 @@ class ArchiMateXMLExporter:
63
64
  model_id: Unique identifier for the model
64
65
  properties: Exported properties per element id (e.g. ``level``), declared as propertyDefinitions
65
66
  views: Diagrams to export (see ``views.build_views``)
67
+ relationship_properties: Exported properties per relationship id, declared with the element ones
66
68
  """
67
69
  # Create root model element
68
70
  root = etree.Element(
@@ -83,7 +85,8 @@ class ArchiMateXMLExporter:
83
85
  self._add_metadata(root)
84
86
 
85
87
  properties = properties or {}
86
- keys = sorted({key for values in properties.values() for key, value in values.items() if value})
88
+ relationship_properties = relationship_properties or {}
89
+ keys = sorted({key for values in [*properties.values(), *relationship_properties.values()] for key, value in values.items() if value})
87
90
  definition_of = {key: f"propid-{key}" for key in keys}
88
91
 
89
92
  # Add elements section
@@ -96,7 +99,7 @@ class ArchiMateXMLExporter:
96
99
  if relationships:
97
100
  rels_section = etree.SubElement(root, "relationships")
98
101
  for relationship in relationships:
99
- self._add_relationship(rels_section, relationship)
102
+ self._add_relationship(rels_section, relationship, relationship_properties.get(relationship.identifier, {}), definition_of)
100
103
 
101
104
  # The exchange format's order: elements, relationships, organizations, propertyDefinitions, views
102
105
  if keys:
@@ -105,7 +108,7 @@ class ArchiMateXMLExporter:
105
108
  definition = etree.SubElement(definitions, "propertyDefinition", attrib={"identifier": definition_of[key], "type": "string"})
106
109
  etree.SubElement(definition, "name").text = key
107
110
  if views:
108
- self._add_views(root, views)
111
+ self._add_views(root, views, definition_of.get("level"))
109
112
 
110
113
  # Write to file
111
114
  tree = etree.ElementTree(root)
@@ -190,8 +193,9 @@ class ArchiMateXMLExporter:
190
193
  value_elem.set(f"{{{self.XML_NS}}}lang", "en")
191
194
  value_elem.text = value
192
195
 
193
- def _add_views(self, root: etree.Element, views: Sequence[View]) -> None:
194
- """Add the diagrams: element nodes with their bounds, relationship connections between them."""
196
+ def _add_views(self, root: etree.Element, views: Sequence[View], level_definition: str | None = None) -> None:
197
+ """Add the diagrams: element nodes with their bounds, relationship connections between them, and each view's
198
+ level as a property where the model declares one."""
195
199
  diagrams = etree.SubElement(etree.SubElement(root, "views"), "diagrams")
196
200
  for view in views:
197
201
  diagram = etree.SubElement(
@@ -202,6 +206,11 @@ class ArchiMateXMLExporter:
202
206
  name_elem = etree.SubElement(diagram, "name")
203
207
  name_elem.set(f"{{{self.XML_NS}}}lang", "en")
204
208
  name_elem.text = view.name
209
+ if level_definition:
210
+ prop = etree.SubElement(etree.SubElement(diagram, "properties"), "property", attrib={"propertyDefinitionRef": level_definition})
211
+ value_elem = etree.SubElement(prop, "value")
212
+ value_elem.set(f"{{{self.XML_NS}}}lang", "en")
213
+ value_elem.text = view.level
205
214
  for node in view.nodes:
206
215
  etree.SubElement(
207
216
  diagram,
@@ -229,13 +238,21 @@ class ArchiMateXMLExporter:
229
238
  },
230
239
  )
231
240
 
232
- def _add_relationship(self, parent: etree.Element, relationship: Relationship) -> None:
241
+ def _add_relationship(
242
+ self,
243
+ parent: etree.Element,
244
+ relationship: Relationship,
245
+ properties: Mapping[str, str] | None = None,
246
+ definition_of: Mapping[str, str] | None = None,
247
+ ) -> None:
233
248
  """
234
249
  Add a relationship to the XML tree.
235
250
 
236
251
  Args:
237
252
  parent: Parent XML element
238
253
  relationship: ArchiMate relationship to add
254
+ properties: The relationship's exported properties (key -> value)
255
+ definition_of: propertyDefinition identifier per key
239
256
  """
240
257
  rel = etree.SubElement(
241
258
  parent,
@@ -258,8 +275,15 @@ class ArchiMateXMLExporter:
258
275
  doc_elem = etree.SubElement(rel, "documentation")
259
276
  doc_elem.text = relationship.documentation
260
277
 
261
- # Note: Properties are not exported as they require propertyDefinitions
262
- # at the model level per ArchiMate Exchange Format XSD.
278
+ # Only the properties passed in are exported (each needs a model-level propertyDefinition), after name and documentation
279
+ shown = sorted((key, value) for key, value in (properties or {}).items() if value and key in (definition_of or {}))
280
+ if shown and definition_of:
281
+ props = etree.SubElement(rel, "properties")
282
+ for key, value in shown:
283
+ prop = etree.SubElement(props, "property", attrib={"propertyDefinitionRef": definition_of[key]})
284
+ value_elem = etree.SubElement(prop, "value")
285
+ value_elem.set(f"{{{self.XML_NS}}}lang", "en")
286
+ value_elem.text = str(value)
263
287
 
264
288
  def export_to_string(
265
289
  self,
@@ -30,6 +30,7 @@ from .db_tool import export_all as export_database
30
30
  from .db_tool import import_all as import_database
31
31
  from .manager import (
32
32
  DB_PATH,
33
+ ConfigDatabaseBusy,
33
34
  ensure_database,
34
35
  get_connection,
35
36
  init_database,
@@ -43,6 +44,7 @@ FUNCTION_MODELS_DIR = DATA_DIR / "function_models"
43
44
 
44
45
  __all__ = [
45
46
  "FUNCTION_MODELS_DIR",
47
+ "ConfigDatabaseBusy",
46
48
  "ensure_database",
47
49
  "get_connection",
48
50
  "init_database",
@@ -28,6 +28,14 @@ SCRIPTS_DIR = Path(__file__).parent / "scripts"
28
28
  DATA_DIR = Path(__file__).parent / "data"
29
29
 
30
30
 
31
+ # How DuckDB reports a database file another process holds (Windows; Linux and macOS)
32
+ _LOCK_MESSAGES = ("being used by another process", "Could not set lock", "Conflicting lock")
33
+
34
+
35
+ class ConfigDatabaseBusy(RuntimeError):
36
+ """The config database is open in another process (DuckDB allows one writer)."""
37
+
38
+
31
39
  def get_connection(read_only: bool = False) -> duckdb.DuckDBPyConnection:
32
40
  """Get a connection to the database.
33
41
 
@@ -37,11 +45,21 @@ def get_connection(read_only: bool = False) -> duckdb.DuckDBPyConnection:
37
45
 
38
46
  Returns:
39
47
  DuckDB connection
48
+
49
+ Raises:
50
+ ConfigDatabaseBusy: Another process (the studio, another CLI run) holds the database
40
51
  """
41
52
  # A fresh install has no database yet: create it from the schema and the shipped seed data
42
53
  if not DB_PATH.exists():
43
54
  ensure_database(DB_PATH)
44
- return duckdb.connect(str(DB_PATH), read_only=read_only)
55
+ try:
56
+ return duckdb.connect(str(DB_PATH), read_only=read_only)
57
+ except duckdb.IOException as e:
58
+ if any(message in str(e) for message in _LOCK_MESSAGES):
59
+ raise ConfigDatabaseBusy(
60
+ f"The config database {DB_PATH} is open in another Deriva process (the studio or another deriva-cli run). Close it, or wait for it to finish, then try again."
61
+ ) from e
62
+ raise
45
63
 
46
64
 
47
65
  def run_sql_file(filepath: Path, conn: duckdb.DuckDBPyConnection | None = None) -> int:
@@ -21,9 +21,13 @@ from .manager import (
21
21
  DEFAULT_DATABASE,
22
22
  GrafeoConnection,
23
23
  close_database,
24
+ database_bytes,
25
+ database_exists,
24
26
  database_file,
27
+ delete_database,
25
28
  engine_info,
26
29
  get_database,
30
+ list_databases,
27
31
  use_database,
28
32
  )
29
33
 
@@ -31,8 +35,12 @@ __all__ = [
31
35
  "DEFAULT_DATABASE",
32
36
  "GrafeoConnection",
33
37
  "close_database",
38
+ "database_bytes",
39
+ "database_exists",
34
40
  "database_file",
41
+ "delete_database",
35
42
  "engine_info",
36
43
  "get_database",
44
+ "list_databases",
37
45
  "use_database",
38
46
  ]
@@ -27,8 +27,10 @@ Usage:
27
27
  from __future__ import annotations
28
28
 
29
29
  import logging
30
+ import shutil
30
31
  import time
31
32
  import weakref
33
+ from pathlib import Path
32
34
  from typing import Any
33
35
 
34
36
  from deriva.common.paths import resolve
@@ -77,6 +79,55 @@ def database_file(key: str) -> str | None:
77
79
  return str(folder / f"{key}.grafeo")
78
80
 
79
81
 
82
+ def _stored_paths(key: str) -> tuple[Path, Path] | None:
83
+ """The key's database file and its write-ahead log folder, never created (None in memory or for an invalid key)."""
84
+ directory = GrafeoSettings().db_dir
85
+ if not directory or not key or key in (".", "..") or any(sep in key for sep in ("/", "\\")):
86
+ return None
87
+ folder = resolve(directory)
88
+ return folder / f"{key}.grafeo", folder / f"{key}.grafeo.wal"
89
+
90
+
91
+ def database_exists(key: str) -> bool:
92
+ """Whether the key's database file exists; never creates it or its folder (False in memory or for an invalid key)."""
93
+ paths = _stored_paths(key)
94
+ return paths is not None and paths[0].exists()
95
+
96
+
97
+ def list_databases() -> list[str]:
98
+ """The keys with a database file in GRAFEO_DB_DIR, sorted (none in memory)."""
99
+ directory = GrafeoSettings().db_dir
100
+ folder = resolve(directory) if directory else None
101
+ if folder is None or not folder.is_dir():
102
+ return []
103
+ return sorted(path.name.removesuffix(".grafeo") for path in folder.glob("*.grafeo") if path.is_file())
104
+
105
+
106
+ def database_bytes(key: str) -> int:
107
+ """Bytes the key's database takes on disk: its file and its write-ahead log (0 when it has none)."""
108
+ paths = _stored_paths(key)
109
+ if paths is None:
110
+ return 0
111
+ database, log = paths
112
+ size = database.stat().st_size if database.is_file() else 0
113
+ return size + (sum(f.stat().st_size for f in log.rglob("*") if f.is_file()) if log.is_dir() else 0)
114
+
115
+
116
+ def delete_database(key: str) -> bool:
117
+ """Delete the key's database file and log; False when it had none. The active database is never deleted (switch
118
+ away first): a query after the delete would create a new, empty file."""
119
+ if key == _db_key:
120
+ raise ValueError(f"The database {key!r} is open; switch to another one before deleting it")
121
+ paths = _stored_paths(key)
122
+ if paths is None or not (paths[0].exists() or paths[1].exists()):
123
+ return False
124
+ database, log = paths
125
+ database.unlink(missing_ok=True)
126
+ if log.is_dir():
127
+ shutil.rmtree(log)
128
+ return True
129
+
130
+
80
131
  def get_database() -> Any:
81
132
  """Get or open the active GrafeoDB instance (see ``use_database``).
82
133
 
@@ -26,7 +26,7 @@ from __future__ import annotations
26
26
 
27
27
  import logging
28
28
  import re
29
- from collections.abc import Iterator
29
+ from collections.abc import Generator
30
30
  from contextlib import contextmanager
31
31
  from dataclasses import dataclass, field
32
32
  from typing import Any
@@ -36,6 +36,7 @@ from deriva.common.ocel import create_edge_id
36
36
  from deriva.common.settings import GraphSettings
37
37
 
38
38
  from .models import (
39
+ BuildModuleNode,
39
40
  BusinessConceptNode,
40
41
  DataDomainNode,
41
42
  DataEntityNode,
@@ -72,6 +73,7 @@ GraphNode = (
72
73
  | ServiceNode
73
74
  | ExternalDependencyNode
74
75
  | DeployableUnitNode
76
+ | BuildModuleNode
75
77
  | DataEntityNode
76
78
  | DataDomainNode
77
79
  | EnvironmentNode
@@ -278,7 +280,7 @@ class GraphManager:
278
280
  raise
279
281
 
280
282
  @contextmanager
281
- def step_tags(self, step: str, version: int | None = None) -> Iterator[None]:
283
+ def step_tags(self, step: str, version: int | None = None) -> Generator[None]:
282
284
  """Nodes and edges written inside the block carry the extraction step (``extractStep``) and its version."""
283
285
  previous = self._step_tags
284
286
  self._step_tags = {"extractStep": step, **({"extractVersion": version} if version is not None else {})}
@@ -288,7 +290,7 @@ class GraphManager:
288
290
  self._step_tags = previous
289
291
 
290
292
  @contextmanager
291
- def recording(self) -> Iterator[GraphWrites]:
293
+ def recording(self) -> Generator[GraphWrites]:
292
294
  """The nodes and edges written inside the block (for the run log); failed writes are left out."""
293
295
  recorder = GraphWrites()
294
296
  self._recorders.append(recorder)
@@ -13,6 +13,10 @@ from typing import Any
13
13
  from . import models
14
14
 
15
15
  __all__ = [
16
+ "GROUP_ORDER",
17
+ "NODE_GROUPS",
18
+ "node_description",
19
+ "node_group",
16
20
  "get_all_node_classes",
17
21
  "get_node_properties",
18
22
  "get_relationship_types",
@@ -36,7 +40,45 @@ def get_all_node_classes() -> dict[str, type]:
36
40
  return node_classes
37
41
 
38
42
 
39
- def get_node_properties(node_class: type) -> list[str]:
43
+ # The intermediate ontology's groups (as ArchiMate has its layers): where a node type's evidence comes from
44
+ GROUP_ORDER = ("structure", "code", "business", "deployment")
45
+ NODE_GROUPS = {
46
+ "Repository": "structure",
47
+ "Directory": "structure",
48
+ "File": "structure",
49
+ "Module": "structure",
50
+ "BuildModule": "structure",
51
+ "TypeDefinition": "code",
52
+ "Method": "code",
53
+ "Test": "code",
54
+ "ExternalDependency": "code",
55
+ "Service": "code",
56
+ "BusinessConcept": "business",
57
+ "DataEntity": "business",
58
+ "DataDomain": "business",
59
+ "Lifecycle": "business",
60
+ "LifecycleEvent": "business",
61
+ "Role": "business",
62
+ "DeployableUnit": "deployment",
63
+ "Deployment": "deployment",
64
+ "Environment": "deployment",
65
+ "Software": "deployment",
66
+ "Technology": "deployment",
67
+ }
68
+
69
+
70
+ def node_group(node_type: str) -> str:
71
+ """The group of a node type (``other`` for a label the metamodel does not name)."""
72
+ return NODE_GROUPS.get(node_type, "other")
73
+
74
+
75
+ def node_description(node_class: type) -> str:
76
+ """The first line of a node class's docstring."""
77
+ lines = (node_class.__doc__ or "").strip().splitlines()
78
+ return lines[0].strip() if lines else ""
79
+
80
+
81
+ def get_node_properties(node_class: type[Any]) -> list[str]:
40
82
  """Get all property names for a node class.
41
83
 
42
84
  Args:
@@ -506,6 +506,45 @@ class ExternalDependencyNode:
506
506
  }
507
507
 
508
508
 
509
+ @dataclass
510
+ class BuildModuleNode:
511
+ """A build module (a folder with a build manifest and code of its own) or a parent system (an aggregator manifest
512
+ that lists modules). Its own files hang below it by ``INCLUDES``, nested modules by ``NESTS``; a deployable unit
513
+ ``BUILDS`` the modules in its folder."""
514
+
515
+ name: str
516
+ path: str
517
+ manifest: str
518
+ repository_name: str
519
+ is_module: bool = False
520
+ is_system: bool = False
521
+ members: list[str] = field(default_factory=list) # For a system: the module paths its manifest lists
522
+ same_as: str | None = None # The unit whose folder it is (one unit there, no module inside): one element with it
523
+ excluded: str | None = None # Why it is no application component (infrastructure-as-code, test-only)
524
+
525
+ def generate_id(self) -> str:
526
+ """Generate a unique ID for this node."""
527
+ return f"BuildModule_{self.path}"
528
+
529
+ def to_dict(self) -> dict:
530
+ """Convert to dictionary for graph storage."""
531
+ return {
532
+ "name": self.name,
533
+ "path": self.path,
534
+ "manifest": self.manifest,
535
+ "isModule": self.is_module,
536
+ "isSystem": self.is_system,
537
+ "members": list(self.members),
538
+ "sameAs": self.same_as,
539
+ "excluded": self.excluded,
540
+ # Its structural name as the one candidate of the closed-set naming (no naming call)
541
+ "nameCandidates": [self.name],
542
+ # Module ids carry no prefix the graph manager reads the repository from
543
+ "repository_name": self.repository_name,
544
+ "type": "BuildModule",
545
+ }
546
+
547
+
509
548
  @dataclass
510
549
  class DeployableUnitNode:
511
550
  """A deployable unit: what runs on its own (a container, a declared entry point, a mainframe application).