pymetadata 0.6.3__tar.gz → 0.6.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 (217) hide show
  1. {pymetadata-0.6.3 → pymetadata-0.6.4}/.bumpversion.toml +1 -1
  2. {pymetadata-0.6.3 → pymetadata-0.6.4}/.github/workflows/ci-cd.yml +2 -2
  3. {pymetadata-0.6.3 → pymetadata-0.6.4}/.github/workflows/docs.yml +1 -1
  4. {pymetadata-0.6.3 → pymetadata-0.6.4}/.github/workflows/ty.yml +1 -1
  5. {pymetadata-0.6.3 → pymetadata-0.6.4}/CITATION.cff +1 -1
  6. {pymetadata-0.6.3 → pymetadata-0.6.4}/PKG-INFO +1 -1
  7. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/annotations.md +8 -0
  8. pymetadata-0.6.4/release-notes/0.6.4.md +13 -0
  9. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/__init__.py +1 -1
  10. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/core/annotation.py +35 -20
  11. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/omex.py +43 -24
  12. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/core/test_annotation.py +79 -0
  13. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/test_omex.py +63 -0
  14. {pymetadata-0.6.3 → pymetadata-0.6.4}/.github/CODEOWNERS +0 -0
  15. {pymetadata-0.6.3 → pymetadata-0.6.4}/.github/dependabot.yml +0 -0
  16. {pymetadata-0.6.3 → pymetadata-0.6.4}/.github/pull_request_template.md +0 -0
  17. {pymetadata-0.6.3 → pymetadata-0.6.4}/.github/rulesets/apply.sh +0 -0
  18. {pymetadata-0.6.3 → pymetadata-0.6.4}/.github/rulesets/develop.json +0 -0
  19. {pymetadata-0.6.3 → pymetadata-0.6.4}/.github/rulesets/main.json +0 -0
  20. {pymetadata-0.6.3 → pymetadata-0.6.4}/.github/rulesets/tags.json +0 -0
  21. {pymetadata-0.6.3 → pymetadata-0.6.4}/.github/workflows/ruff.yml +0 -0
  22. {pymetadata-0.6.3 → pymetadata-0.6.4}/.gitignore +0 -0
  23. {pymetadata-0.6.3 → pymetadata-0.6.4}/.pre-commit-config.yaml +0 -0
  24. {pymetadata-0.6.3 → pymetadata-0.6.4}/.python-version +0 -0
  25. {pymetadata-0.6.3 → pymetadata-0.6.4}/.ruff.toml +0 -0
  26. {pymetadata-0.6.3 → pymetadata-0.6.4}/.zenodo.json +0 -0
  27. {pymetadata-0.6.3 → pymetadata-0.6.4}/CLAUDE.md +0 -0
  28. {pymetadata-0.6.3 → pymetadata-0.6.4}/LICENSE +0 -0
  29. {pymetadata-0.6.3 → pymetadata-0.6.4}/README.md +0 -0
  30. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/README.md +0 -0
  31. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/cache.md +0 -0
  32. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/console.md +0 -0
  33. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/core.annotation.md +0 -0
  34. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/core.creator.md +0 -0
  35. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/core.miriam.md +0 -0
  36. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/core.xref.md +0 -0
  37. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/index.md +0 -0
  38. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/log.md +0 -0
  39. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/omex.md +0 -0
  40. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/ontologies.term.md +0 -0
  41. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/webservices.chebi.md +0 -0
  42. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/webservices.ols.md +0 -0
  43. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/webservices.registry.md +0 -0
  44. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/webservices.unichem.md +0 -0
  45. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/api/webservices.webservice.md +0 -0
  46. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/development.md +0 -0
  47. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/images/HARMONY2026_slides.png +0 -0
  48. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/images/favicon/about.txt +0 -0
  49. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/images/favicon/android-chrome-192x192.png +0 -0
  50. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/images/favicon/android-chrome-512x512.png +0 -0
  51. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/images/favicon/apple-touch-icon.png +0 -0
  52. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/images/favicon/favicon-16x16.png +0 -0
  53. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/images/favicon/favicon-32x32.png +0 -0
  54. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/images/favicon/favicon.ico +0 -0
  55. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/images/favicon/favicon_io.zip +0 -0
  56. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/images/favicon/pymetadata-100x100-300dpi.png +0 -0
  57. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/images/favicon/site.webmanifest +0 -0
  58. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/index.md +0 -0
  59. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/installation.md +0 -0
  60. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/omex.md +0 -0
  61. {pymetadata-0.6.3 → pymetadata-0.6.4}/docs/robots.txt +0 -0
  62. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/annotation.py +0 -0
  63. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/cache_path.py +0 -0
  64. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/MODEL2402290004.4_pymetadata.omex +0 -0
  65. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/create_omex.py +0 -0
  66. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/BioModelsMetadata_MODEL2402290004.csv +0 -0
  67. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoAcuteBZ1.m +0 -0
  68. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoAcuteBZ2.m +0 -0
  69. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoAcuteBZ3.m +0 -0
  70. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoChronicBZ.m +0 -0
  71. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoChronicRZ1.m +0 -0
  72. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoChronicRZ2.m +0 -0
  73. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoNZ.m +0 -0
  74. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/POM253.mat +0 -0
  75. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/PlotAPCaTTaAcuteAndChronic.m +0 -0
  76. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/Zhou2024_Updated-model.m +0 -0
  77. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/getCurrentsStructureCaMKSKJrelpKinetics.m +0 -0
  78. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/getStartingState.m +0 -0
  79. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/main.m +0 -0
  80. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/manifest.xml +0 -0
  81. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/metadata.rdf +0 -0
  82. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/modelRunnerCaMKSKJrelpKinetics1ms.m +0 -0
  83. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/model_ToRORd_LandCaMKSKJrelpKinetics.m +0 -0
  84. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/omex.py +0 -0
  85. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/omex_from_url.py +0 -0
  86. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/omex/test.omex +0 -0
  87. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/pbpko.py +0 -0
  88. {pymetadata-0.6.3 → pymetadata-0.6.4}/examples/sbo.py +0 -0
  89. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/.gitignore +0 -0
  90. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/README.md +0 -0
  91. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/_extensions/grantmcdermott/clean/_extension.yml +0 -0
  92. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/_extensions/grantmcdermott/clean/clean.scss +0 -0
  93. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/_extensions/grantmcdermott/clean/mathjax-config.js +0 -0
  94. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/custom.scss +0 -0
  95. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/images/annotation_example.png +0 -0
  96. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/images/annotation_resolved.png +0 -0
  97. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/images/annotation_resolved_small.png +0 -0
  98. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/images/github_screenshot.png +0 -0
  99. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/images/headshot.png +0 -0
  100. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/images/omex_logo.png +0 -0
  101. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/images/omex_overview.png +0 -0
  102. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/images/pymetadata.png +0 -0
  103. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/images/pymetadata_large.png +0 -0
  104. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/images/qr_livermetabolism.png +0 -0
  105. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/mathjax-config.js +0 -0
  106. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/pymetadata.bib +0 -0
  107. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/pymetadata.qmd +0 -0
  108. {pymetadata-0.6.3 → pymetadata-0.6.4}/presentations/HARMONY2026/styles.css +0 -0
  109. {pymetadata-0.6.3 → pymetadata-0.6.4}/pyproject.toml +0 -0
  110. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.10.md +0 -0
  111. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.11.md +0 -0
  112. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.12.md +0 -0
  113. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.13.md +0 -0
  114. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.14.md +0 -0
  115. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.15.md +0 -0
  116. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.16.md +0 -0
  117. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.17.md +0 -0
  118. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.18.md +0 -0
  119. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.19.md +0 -0
  120. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.2.md +0 -0
  121. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.20.md +0 -0
  122. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.21.md +0 -0
  123. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.22.md +0 -0
  124. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.3.md +0 -0
  125. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.4.md +0 -0
  126. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.5.md +0 -0
  127. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.6.md +0 -0
  128. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.7.md +0 -0
  129. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.8.md +0 -0
  130. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.0.9.md +0 -0
  131. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.1.0.md +0 -0
  132. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.2.1.md +0 -0
  133. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.2.10.md +0 -0
  134. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.2.2.md +0 -0
  135. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.2.3.md +0 -0
  136. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.2.4.md +0 -0
  137. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.2.5.md +0 -0
  138. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.2.6.md +0 -0
  139. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.2.7.md +0 -0
  140. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.2.8.md +0 -0
  141. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.2.9.md +0 -0
  142. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.3.0.md +0 -0
  143. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.3.1.md +0 -0
  144. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.3.10.md +0 -0
  145. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.3.11.md +0 -0
  146. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.3.2.md +0 -0
  147. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.3.3.md +0 -0
  148. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.3.4.md +0 -0
  149. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.3.5.md +0 -0
  150. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.3.6.md +0 -0
  151. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.3.7.md +0 -0
  152. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.3.8.md +0 -0
  153. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.3.9.md +0 -0
  154. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.4.0.md +0 -0
  155. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.4.1.md +0 -0
  156. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.4.2.md +0 -0
  157. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.4.3.md +0 -0
  158. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.4.4.md +0 -0
  159. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.0.md +0 -0
  160. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.1.md +0 -0
  161. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.10.md +0 -0
  162. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.11.md +0 -0
  163. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.12.md +0 -0
  164. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.2.md +0 -0
  165. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.3.md +0 -0
  166. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.4.md +0 -0
  167. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.5.md +0 -0
  168. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.6.md +0 -0
  169. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.7.md +0 -0
  170. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.8.md +0 -0
  171. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.5.9.md +0 -0
  172. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.6.0.md +0 -0
  173. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.6.1.md +0 -0
  174. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.6.2.md +0 -0
  175. {pymetadata-0.6.3 → pymetadata-0.6.4}/release-notes/0.6.3.md +0 -0
  176. {pymetadata-0.6.3 → pymetadata-0.6.4}/scripts/llms_txt.py +0 -0
  177. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/cache.py +0 -0
  178. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/console.py +0 -0
  179. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/core/__init__.py +0 -0
  180. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/core/creator.py +0 -0
  181. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/core/miriam.py +0 -0
  182. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/core/synonym.py +0 -0
  183. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/core/xref.py +0 -0
  184. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/log.py +0 -0
  185. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/ontologies/__init__.py +0 -0
  186. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/ontologies/_ontology_builder.py +0 -0
  187. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/ontologies/kisao.py +0 -0
  188. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/ontologies/pbpko.py +0 -0
  189. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/ontologies/sbo.py +0 -0
  190. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/ontologies/term.py +0 -0
  191. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/py.typed +0 -0
  192. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/resources/ontologies/README.md +0 -0
  193. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/webservices/__init__.py +0 -0
  194. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/webservices/chebi.py +0 -0
  195. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/webservices/ols.py +0 -0
  196. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/webservices/registry.py +0 -0
  197. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/webservices/unichem.py +0 -0
  198. {pymetadata-0.6.3 → pymetadata-0.6.4}/src/pymetadata/webservices/webservice.py +0 -0
  199. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/core/test_creator.py +0 -0
  200. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/data/omex/BIOMD0000000001.omex +0 -0
  201. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/data/omex/CombineArchiveShowCase.omex +0 -0
  202. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/data/omex/CombineArchiveShowCase_manifest.xml +0 -0
  203. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/data/omex/CompModels.omex +0 -0
  204. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/data/omex/CompModels_manifest.xml +0 -0
  205. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/data/omex/iCGB21FR.omex +0 -0
  206. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/test_cache.py +0 -0
  207. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/test_chebi.py +0 -0
  208. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/test_log.py +0 -0
  209. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/test_offline.py +0 -0
  210. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/test_ols.py +0 -0
  211. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/test_ontology.py +0 -0
  212. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/test_registry.py +0 -0
  213. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/test_sbo_kisao.py +0 -0
  214. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/test_unichem.py +0 -0
  215. {pymetadata-0.6.3 → pymetadata-0.6.4}/tests/test_webservice.py +0 -0
  216. {pymetadata-0.6.3 → pymetadata-0.6.4}/tox.ini +0 -0
  217. {pymetadata-0.6.3 → pymetadata-0.6.4}/zensical.toml +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "0.6.3"
2
+ current_version = "0.6.4"
3
3
  commit = true
4
4
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
5
5
  serialize = ["{major}.{minor}.{patch}"]
@@ -52,7 +52,7 @@ jobs:
52
52
  persist-credentials: false
53
53
  - name: Install uv and set the python version
54
54
  # astral-sh/setup-uv publishes no floating major tag beyond v7
55
- uses: astral-sh/setup-uv@v10.0.1
55
+ uses: astral-sh/setup-uv@v10.1.0
56
56
  with:
57
57
  python-version: ${{ matrix.python-version }}
58
58
  enable-cache: true
@@ -92,7 +92,7 @@ jobs:
92
92
  with:
93
93
  persist-credentials: false
94
94
  - name: Install uv
95
- uses: astral-sh/setup-uv@v10.0.1
95
+ uses: astral-sh/setup-uv@v10.1.0
96
96
  with:
97
97
  python-version: "3.14"
98
98
  enable-cache: true
@@ -29,7 +29,7 @@ jobs:
29
29
  - name: Configure GitHub Pages
30
30
  uses: actions/configure-pages@v6
31
31
  - name: Install uv and set the python version
32
- uses: astral-sh/setup-uv@v10.0.1
32
+ uses: astral-sh/setup-uv@v10.1.0
33
33
  with:
34
34
  python-version: "3.14"
35
35
  enable-cache: true
@@ -26,7 +26,7 @@ jobs:
26
26
  with:
27
27
  persist-credentials: false
28
28
  - name: Install uv and set the python version
29
- uses: astral-sh/setup-uv@v10.0.1
29
+ uses: astral-sh/setup-uv@v10.1.0
30
30
  with:
31
31
  python-version: "3.14"
32
32
  enable-cache: true
@@ -8,7 +8,7 @@ authors:
8
8
  given-names: "Matthias"
9
9
  orcid: "https://orcid.org/0000-0003-1725-179X"
10
10
  affiliation: "Humboldt-University Berlin, Faculty of Life Science, Institute for Biology, Biology, Berlin; University Lübeck; University Hospital Schleswig-Holstein, Campus Lübeck, First Department of Medicine, Germany"
11
- version: "0.6.3"
11
+ version: "0.6.4"
12
12
  date-released: "2026-09-07"
13
13
  doi: "10.5281/zenodo.5308801"
14
14
  license: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pymetadata
3
- Version: 0.6.3
3
+ Version: 0.6.4
4
4
  Summary: pymetadata are python utilities for working with metadata.
5
5
  Project-URL: Homepage, https://matthiaskoenig.github.io/pymetadata
6
6
  Project-URL: Documentation, https://matthiaskoenig.github.io/pymetadata
@@ -51,6 +51,14 @@ RDFAnnotation(qualifier=BQB.IS, resource="https://en.wikipedia.org/wiki/Cytosol"
51
51
 
52
52
  `resource_normalized` returns `https://identifiers.org/<prefix>:<accession>`. For namespaces which embed their prefix in the identifier (GO, CHEBI, SBO, BTO) the prefix is already part of the term and is not added a second time.
53
53
 
54
+ The normalization never changes what a resource says: `resource_normalized` is a URL, and parsing it again gives the same `collection` and `term`. A collection which is not in the identifiers.org registry, or a term which does not carry the prefix of its collection, cannot be written as a compact identifier and is returned as `https://identifiers.org/<collection>/<term>`:
55
+
56
+ ```python
57
+ annotation = RDFAnnotation(qualifier=BQB.IS, resource="urn:miriam:sabiork:1406")
58
+ print(annotation.resource_normalized)
59
+ # https://identifiers.org/sabiork/1406
60
+ ```
61
+
54
62
  ## Validation
55
63
 
56
64
  Validation answers two questions: is the qualifier a real MIRIAM qualifier, and does the term match the pattern the identifiers.org registry defines for its collection?
@@ -0,0 +1,13 @@
1
+ # Release notes for pymetadata 0.6.4
2
+ ![pymetadata](./docs/images/favicon/pymetadata-100x100-300dpi.png)
3
+
4
+ We are pleased to release the next version of pymetadata including the
5
+ following changes:
6
+
7
+ ## Fixes
8
+ - `Omex.from_directory`, and with it `Omex.from_omex` and `Omex.from_url`, add the entries in the order of the manifest, followed by the files which the manifest does not list, sorted by their location. The entries were added in the order of `os.walk`, which is the order of the file system, so `manifest.entries` and `entries_by_format` of the same archive differed between machines
9
+ - `RDFAnnotation.resource_normalized` keeps the collection and the term of every resource: the result is a URL, and parsing it again gives the same `collection` and `term`. A collection which is not in the identifiers.org registry (`http://identifiers.org/sabiork/1406`, `urn:miriam:foo:bar`) was reduced to the bare term, and a term which does not match the pattern of its collection (`http://identifiers.org/chebi/000000035`) lost its collection. Both are now returned as `https://identifiers.org/<collection>/<term>`, and the compact identifier of a prefix which is not in the registry stays the URL `https://identifiers.org/CMO:0000012` (#81)
10
+ - identifiers.org URLs of a prefix with a hyphen or an underscore, like `ec-code`, `pdb-ccd` or `go_ref`, are parsed into collection and term instead of being kept as an arbitrary URL (#81)
11
+ - the collection of a MIRIAM URN and of the `collection/term` notation is lowercased, as it already was for URLs, so `TAXONOMY/9606` is the collection `taxonomy` (#81)
12
+
13
+ Your pymetadata team
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
  logging.getLogger(__name__).addHandler(logging.NullHandler())
8
8
 
9
9
  __author__ = "Matthias Koenig"
10
- __version__ = "0.6.3"
10
+ __version__ = "0.6.4"
11
11
 
12
12
 
13
13
  program_name: str = "pymetadata"
@@ -48,11 +48,13 @@ def get_ols_query() -> OLSQuery:
48
48
 
49
49
 
50
50
  IDENTIFIERS_ORG_PREFIX: Final = "https://identifiers.org"
51
+ # prefixes of the registry consist of letters, digits, `.`, `_` and `-`,
52
+ # e.g., `ec-code` or `go_ref`
51
53
  IDENTIFIERS_ORG_PATTERN_COMPACT: Final = re.compile(
52
- r"^https?://identifiers.org/([a-zA-Z0-9.]+):(.+)"
54
+ r"^https?://identifiers.org/([a-zA-Z0-9._-]+):(.+)"
53
55
  )
54
56
  IDENTIFIERS_ORG_PATTERN_CLASSIC: Final = re.compile(
55
- r"^https?://identifiers.org/([a-zA-Z0-9.]+)/(.+)"
57
+ r"^https?://identifiers.org/([a-zA-Z0-9._-]+)/(.+)"
56
58
  )
57
59
 
58
60
  BIOREGISTRY_PREFIX: Final = "https://bioregistry.io"
@@ -167,21 +169,15 @@ class RDFAnnotation:
167
169
  match3 = MIRIAM_URN_PATTERN.match(resource)
168
170
  if match3:
169
171
  tokens = match3.group(1).split(":")
170
- self.collection = tokens[0]
172
+ self.collection = tokens[0].lower()
171
173
  self.term = ":".join(tokens[1:]).replace("%3A", ":")
172
174
  self.provider = ProviderType.IDENTIFIERS_ORG
173
175
 
174
- logger.warning(
175
- "Deprecated urn pattern `%s` updated: %s",
176
- resource,
177
- self.resource_normalized,
178
- )
179
-
180
176
  else:
181
177
  # handle short notation
182
178
  tokens = resource.split("/")
183
179
  if len(tokens) > 1:
184
- self.collection = tokens[0]
180
+ self.collection = tokens[0].lower()
185
181
  self.term = "/".join(tokens[1:])
186
182
  self.provider = ProviderType.IDENTIFIERS_ORG
187
183
  elif len(tokens) == 1 and ":" in tokens[0]:
@@ -209,6 +205,13 @@ class RDFAnnotation:
209
205
  if self.collection in self.replaced_collections:
210
206
  self.collection = self.replaced_collections[self.collection]
211
207
 
208
+ if resource.startswith("urn:miriam:"):
209
+ logger.warning(
210
+ "Deprecated urn pattern `%s` updated: %s",
211
+ resource,
212
+ self.resource_normalized,
213
+ )
214
+
212
215
  if validate:
213
216
  self.validate()
214
217
 
@@ -244,21 +247,33 @@ class RDFAnnotation:
244
247
  `https://identifiers.org/<prefix>:<accession>`. If the namespace is
245
248
  embedded in the LUI the prefix is already part of the term, otherwise
246
249
  the prefix of the identifiers.org registry is prepended.
250
+
251
+ The normalization never changes what the resource says: the result is
252
+ an url, and parsing it again yields the same `collection` and `term`.
253
+ A collection which is not in the registry, or a term which does not
254
+ carry the prefix of its collection, can not be written as a compact
255
+ identifier and is returned as
256
+ `https://identifiers.org/<collection>/<term>`. Whether the term is
257
+ valid does not matter here, this is the task of `validate`.
258
+
259
+ Resources without a collection, i.e., arbitrary urls, are returned
260
+ unchanged.
247
261
  """
248
262
  if not self.term:
249
263
  return None
250
264
 
251
- if (
252
- self.provider == ProviderType.IDENTIFIERS_ORG
253
- and self.collection is not None
254
- ):
255
- namespace = get_registry().ns_dict.get(self.collection, None)
256
- if namespace:
257
- if namespace.namespaceEmbeddedInLui:
258
- return f"{IDENTIFIERS_ORG_PREFIX}/{self.term}"
259
- return f"{IDENTIFIERS_ORG_PREFIX}/{self.collection}:{self.term}"
265
+ if self.provider != ProviderType.IDENTIFIERS_ORG or self.collection is None:
266
+ return self.term
267
+
268
+ namespace = get_registry().ns_dict.get(self.collection, None)
269
+ if namespace and not namespace.namespaceEmbeddedInLui:
270
+ return f"{IDENTIFIERS_ORG_PREFIX}/{self.collection}:{self.term}"
271
+
272
+ # the term is only a compact identifier if its prefix is the collection
273
+ if self.term.lower().startswith(f"{self.collection}:"):
274
+ return f"{IDENTIFIERS_ORG_PREFIX}/{self.term}"
260
275
 
261
- return self.term
276
+ return f"{IDENTIFIERS_ORG_PREFIX}/{self.collection}/{self.term}"
262
277
 
263
278
  def __repr__(self) -> str:
264
279
  """Get representation string."""
@@ -743,6 +743,10 @@ class Omex:
743
743
  `Omex.guess_format`; SED-ML files added this way get `master=True`,
744
744
  as they are the entry point of a simulation study.
745
745
 
746
+ The entries are in the order of the manifest, followed by the files
747
+ which the manifest does not list, sorted by their location. The order
748
+ does not depend on the file system.
749
+
746
750
  Args:
747
751
  directory: directory with the content of the archive
748
752
 
@@ -785,7 +789,8 @@ class Omex:
785
789
  # new archive
786
790
  omex = Omex()
787
791
 
788
- # iterate over all locations and add entry
792
+ # locations of all files
793
+ file_paths: dict[str, str] = {}
789
794
  for root, _dirs, files in os.walk(str(directory)):
790
795
  for file in files:
791
796
  file_path = os.path.join(root, file)
@@ -795,32 +800,46 @@ class Omex:
795
800
  if location == "./manifest.xml":
796
801
  # manifest is created from the internal manifest entries
797
802
  continue
803
+ file_paths[location] = file_path
804
+
805
+ # The order of `os.walk` is the order of the file system, which differs
806
+ # between machines: the entries are added in the order of the manifest,
807
+ # followed by the files which the manifest does not list, by location.
808
+ positions: dict[str, int] = {}
809
+ if manifest:
810
+ positions = {e.location: k for k, e in enumerate(manifest.entries)}
811
+ locations = sorted(
812
+ file_paths, key=lambda loc: (positions.get(loc, len(positions)), loc)
813
+ )
798
814
 
799
- logger.debug("'%s' -> '%s'", file_path, location)
800
- entry: ManifestEntry
801
- if manifest and location in manifest:
802
- # use entry from existing manifest
803
- entry = manifest[location]
804
- else:
805
- if manifest and location not in manifest:
806
- logger.warning(
807
- "Entry with location missing in manifest.xml: '%s'",
808
- location,
809
- )
810
-
811
- format = Omex.guess_format(Path(file_path))
812
- master = False
813
- if format and ManifestEntry.is_format(
814
- format_key="sedml", format=format
815
- ):
816
- master = True
817
- entry = ManifestEntry(
818
- location=location,
819
- format=format,
820
- master=master,
815
+ # iterate over all locations and add entry
816
+ for location in locations:
817
+ file_path = file_paths[location]
818
+ logger.debug("'%s' -> '%s'", file_path, location)
819
+ entry: ManifestEntry
820
+ if manifest and location in manifest:
821
+ # use entry from existing manifest
822
+ entry = manifest[location]
823
+ else:
824
+ if manifest and location not in manifest:
825
+ logger.warning(
826
+ "Entry with location missing in manifest.xml: '%s'",
827
+ location,
821
828
  )
822
829
 
823
- omex.add_entry(entry_path=Path(file_path), entry=entry)
830
+ format = Omex.guess_format(Path(file_path))
831
+ master = False
832
+ if format and ManifestEntry.is_format(
833
+ format_key="sedml", format=format
834
+ ):
835
+ master = True
836
+ entry = ManifestEntry(
837
+ location=location,
838
+ format=format,
839
+ master=master,
840
+ )
841
+
842
+ omex.add_entry(entry_path=Path(file_path), entry=entry)
824
843
 
825
844
  return omex
826
845
 
@@ -227,3 +227,82 @@ def test_resource_normalized(resource: str, expected: str) -> None:
227
227
  """Test normalization to identifiers.org compact identifiers."""
228
228
  a = RDFAnnotation(qualifier=BQB.IS, resource=resource)
229
229
  assert a.resource_normalized == expected
230
+
231
+
232
+ resource_kept_data = [
233
+ # collection which is not in the registry (see #81)
234
+ ("http://identifiers.org/sabiork/1406", "https://identifiers.org/sabiork/1406"),
235
+ ("urn:miriam:foo:bar", "https://identifiers.org/foo/bar"),
236
+ ("foo/bar", "https://identifiers.org/foo/bar"),
237
+ # collection which is not in the registry, term with a prefix of its own
238
+ (
239
+ "http://identifiers.org/unit/UO:0000040",
240
+ "https://identifiers.org/unit/UO:0000040",
241
+ ),
242
+ # compact identifier of a prefix which is not in the registry
243
+ ("https://identifiers.org/CMO:0000012", "https://identifiers.org/CMO:0000012"),
244
+ ("CMO:0000012", "https://identifiers.org/CMO:0000012"),
245
+ # term which does not match the pattern of its collection
246
+ (
247
+ "http://identifiers.org/chebi/000000035",
248
+ "https://identifiers.org/chebi/000000035",
249
+ ),
250
+ ("taxonomy/abc", "https://identifiers.org/taxonomy:abc"),
251
+ # prefix with a hyphen or an underscore
252
+ ("urn:miriam:ec-code:1.1.1.1", "https://identifiers.org/ec-code:1.1.1.1"),
253
+ (
254
+ "https://identifiers.org/ec-code/1.1.1.1",
255
+ "https://identifiers.org/ec-code:1.1.1.1",
256
+ ),
257
+ ("go_ref/GO_REF:0000041", "https://identifiers.org/GO_REF:0000041"),
258
+ (
259
+ "https://identifiers.org/GO_REF:0000041",
260
+ "https://identifiers.org/GO_REF:0000041",
261
+ ),
262
+ # prefixes are case insensitive
263
+ ("urn:miriam:CHEBI:CHEBI%3A33699", "https://identifiers.org/CHEBI:33699"),
264
+ ("TAXONOMY/9606", "https://identifiers.org/taxonomy:9606"),
265
+ ]
266
+
267
+
268
+ def assert_resource_kept(resource: str) -> None:
269
+ """Assert that the normalized resource is a url with the same meaning."""
270
+ a = RDFAnnotation(qualifier=BQB.IS, resource=resource, validate=False)
271
+ normalized = a.resource_normalized
272
+ assert normalized
273
+ assert normalized.startswith("https://")
274
+
275
+ b = RDFAnnotation(qualifier=BQB.IS, resource=normalized, validate=False)
276
+ assert (b.collection, b.term) == (a.collection, a.term)
277
+ assert b.resource_normalized == normalized
278
+
279
+
280
+ @pytest.mark.parametrize("resource,expected", resource_kept_data)
281
+ def test_resource_normalized_keeps_resource(resource: str, expected: str) -> None:
282
+ """Test that collection and term survive the normalization, see #81."""
283
+ a = RDFAnnotation(qualifier=BQB.IS, resource=resource, validate=False)
284
+ assert a.resource_normalized == expected
285
+ assert_resource_kept(resource)
286
+
287
+
288
+ @pytest.mark.parametrize(
289
+ "resource", [resource for resource, _ in resource_normalized_data]
290
+ )
291
+ def test_resource_normalized_roundtrip(resource: str) -> None:
292
+ """Test that the normalized resource parses to the same annotation."""
293
+ assert_resource_kept(resource)
294
+
295
+
296
+ def test_resource_normalized_registry() -> None:
297
+ """Test the normalization with the sample of every registry namespace."""
298
+ from pymetadata.webservices.registry import get_registry
299
+
300
+ for prefix, namespace in get_registry().ns_dict.items():
301
+ sample_id = namespace.sampleId
302
+ if not sample_id:
303
+ continue
304
+ for resource in [
305
+ f"https://identifiers.org/{prefix}/{sample_id}",
306
+ f"urn:miriam:{prefix}:{sample_id.replace(':', '%3A')}",
307
+ ]:
308
+ assert_resource_kept(resource)
@@ -272,3 +272,66 @@ def test_manifest_without_namespace(tmp_path: Path) -> None:
272
272
  manifest = Manifest.from_manifest(manifest_path)
273
273
  assert len(manifest) == 2
274
274
  assert manifest["./model.xml"].master is True
275
+
276
+
277
+ # locations of a manifest in an order which is neither sorted nor the order in
278
+ # which the files are created, so that no file system returns it by chance
279
+ ORDERED_LOCATIONS = [
280
+ f"./{directory}{name}.xml"
281
+ for name in ["m", "b", "z", "a", "q", "c", "y", "d"]
282
+ for directory in ["models/", "", "data/"]
283
+ ]
284
+
285
+
286
+ def _ordered_directory(directory: Path, locations: list[str]) -> Path:
287
+ """Write the files of all locations and a manifest which lists `locations`."""
288
+ for location in sorted(ORDERED_LOCATIONS):
289
+ path = directory / location
290
+ path.parent.mkdir(parents=True, exist_ok=True)
291
+ path.write_text("<sbml/>")
292
+
293
+ manifest = Manifest()
294
+ for location in locations:
295
+ manifest.add_entry(ManifestEntry(location=location, format="sbml"))
296
+ manifest.to_manifest(directory / "manifest.xml")
297
+ return directory
298
+
299
+
300
+ def test_from_directory_keeps_manifest_order(tmp_path: Path) -> None:
301
+ """The entries of a directory are in the order of its manifest.
302
+
303
+ The order in which the file system returns the files differs between
304
+ machines and must not be the order of the entries.
305
+ """
306
+ directory = _ordered_directory(tmp_path, ORDERED_LOCATIONS)
307
+ omex = Omex.from_directory(directory)
308
+ locations = [entry.location for entry in omex.manifest.entries]
309
+ assert locations == [".", "./manifest.xml", *ORDERED_LOCATIONS]
310
+
311
+
312
+ def test_from_directory_appends_unlisted_files(tmp_path: Path) -> None:
313
+ """Files which the manifest does not list follow the listed ones, sorted."""
314
+ listed = ORDERED_LOCATIONS[:5]
315
+ directory = _ordered_directory(tmp_path, listed)
316
+ omex = Omex.from_directory(directory)
317
+ locations = [entry.location for entry in omex.manifest.entries]
318
+ unlisted = sorted(set(ORDERED_LOCATIONS) - set(listed))
319
+ assert locations == [".", "./manifest.xml", *listed, *unlisted]
320
+
321
+
322
+ def test_from_directory_without_manifest_is_sorted(tmp_path: Path) -> None:
323
+ """Without a manifest the entries are sorted by their location."""
324
+ directory = _ordered_directory(tmp_path, [])
325
+ (directory / "manifest.xml").unlink()
326
+ omex = Omex.from_directory(directory)
327
+ locations = [entry.location for entry in omex.manifest.entries]
328
+ assert locations == [".", "./manifest.xml", *sorted(ORDERED_LOCATIONS)]
329
+
330
+
331
+ def test_omex_roundtrip_keeps_manifest_order(tmp_path: Path) -> None:
332
+ """The order of the entries survives writing and reading an archive."""
333
+ omex = Omex.from_directory(_ordered_directory(tmp_path / "in", ORDERED_LOCATIONS))
334
+ omex_path = tmp_path / "ordered.omex"
335
+ omex.to_omex(omex_path)
336
+ locations = [entry.location for entry in Omex.from_omex(omex_path).manifest.entries]
337
+ assert locations == [".", "./manifest.xml", *ORDERED_LOCATIONS]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes