pymetadata 0.5.3__tar.gz → 0.5.5__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.

Potentially problematic release.


This version of pymetadata might be problematic. Click here for more details.

Files changed (144) hide show
  1. {pymetadata-0.5.3 → pymetadata-0.5.5}/.bumpversion.toml +1 -1
  2. {pymetadata-0.5.3 → pymetadata-0.5.5}/.gitignore +1 -0
  3. pymetadata-0.5.5/.python-version +1 -0
  4. {pymetadata-0.5.3 → pymetadata-0.5.5}/PKG-INFO +7 -8
  5. {pymetadata-0.5.3 → pymetadata-0.5.5}/docs/composite_annotations.md +1 -1
  6. {pymetadata-0.5.3 → pymetadata-0.5.5}/pyproject.toml +6 -7
  7. pymetadata-0.5.5/release-notes/0.5.4.md +11 -0
  8. pymetadata-0.5.5/release-notes/0.5.5.md +13 -0
  9. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/__init__.py +1 -1
  10. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/chebi.py +21 -29
  11. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/core/annotation.py +114 -53
  12. pymetadata-0.5.5/src/pymetadata/identifiers/registry.py +173 -0
  13. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/metadata/eco.py +882 -301
  14. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/core/test_annotation.py +43 -23
  15. pymetadata-0.5.3/.github/CONTRIBUTING.rst +0 -141
  16. pymetadata-0.5.3/.github/ISSUE_TEMPLATE/01-bug-report.md +0 -49
  17. pymetadata-0.5.3/.github/ISSUE_TEMPLATE/02-question.md +0 -15
  18. pymetadata-0.5.3/.github/ISSUE_TEMPLATE/03-feature-request.md +0 -36
  19. pymetadata-0.5.3/.github/ISSUE_TEMPLATE/config.yml +0 -2
  20. pymetadata-0.5.3/.github/PULL_REQUEST_TEMPLATE.md +0 -4
  21. pymetadata-0.5.3/.github/SUPPORT.rst +0 -6
  22. pymetadata-0.5.3/.python-version +0 -1
  23. pymetadata-0.5.3/src/pymetadata/identifiers/registry.py +0 -375
  24. pymetadata-0.5.3/src/pymetadata/resources/chebi_webservice_wsdl.xml +0 -509
  25. pymetadata-0.5.3/uv.lock +0 -1179
  26. {pymetadata-0.5.3 → pymetadata-0.5.5}/.github/workflows/main.yml +0 -0
  27. {pymetadata-0.5.3 → pymetadata-0.5.5}/.github/workflows/mypy.yml +0 -0
  28. {pymetadata-0.5.3 → pymetadata-0.5.5}/.github/workflows/ruff.yml +0 -0
  29. {pymetadata-0.5.3 → pymetadata-0.5.5}/.pre-commit-config.yaml +0 -0
  30. {pymetadata-0.5.3 → pymetadata-0.5.5}/.ruff.toml +0 -0
  31. {pymetadata-0.5.3 → pymetadata-0.5.5}/.zenodo.json +0 -0
  32. {pymetadata-0.5.3 → pymetadata-0.5.5}/LICENSE +0 -0
  33. {pymetadata-0.5.3 → pymetadata-0.5.5}/README.md +0 -0
  34. {pymetadata-0.5.3 → pymetadata-0.5.5}/RELEASE.md +0 -0
  35. {pymetadata-0.5.3 → pymetadata-0.5.5}/docs/images/favicon/about.txt +0 -0
  36. {pymetadata-0.5.3 → pymetadata-0.5.5}/docs/images/favicon/android-chrome-192x192.png +0 -0
  37. {pymetadata-0.5.3 → pymetadata-0.5.5}/docs/images/favicon/android-chrome-512x512.png +0 -0
  38. {pymetadata-0.5.3 → pymetadata-0.5.5}/docs/images/favicon/apple-touch-icon.png +0 -0
  39. {pymetadata-0.5.3 → pymetadata-0.5.5}/docs/images/favicon/favicon-16x16.png +0 -0
  40. {pymetadata-0.5.3 → pymetadata-0.5.5}/docs/images/favicon/favicon-32x32.png +0 -0
  41. {pymetadata-0.5.3 → pymetadata-0.5.5}/docs/images/favicon/favicon.ico +0 -0
  42. {pymetadata-0.5.3 → pymetadata-0.5.5}/docs/images/favicon/favicon_io.zip +0 -0
  43. {pymetadata-0.5.3 → pymetadata-0.5.5}/docs/images/favicon/pymetadata-100x100-300dpi.png +0 -0
  44. {pymetadata-0.5.3 → pymetadata-0.5.5}/docs/images/favicon/site.webmanifest +0 -0
  45. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.10.md +0 -0
  46. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.11.md +0 -0
  47. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.12.md +0 -0
  48. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.13.md +0 -0
  49. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.14.md +0 -0
  50. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.15.md +0 -0
  51. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.16.md +0 -0
  52. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.17.md +0 -0
  53. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.18.md +0 -0
  54. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.19.md +0 -0
  55. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.2.md +0 -0
  56. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.20.md +0 -0
  57. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.21.md +0 -0
  58. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.22.md +0 -0
  59. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.3.md +0 -0
  60. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.4.md +0 -0
  61. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.5.md +0 -0
  62. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.6.md +0 -0
  63. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.7.md +0 -0
  64. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.8.md +0 -0
  65. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.0.9.md +0 -0
  66. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.1.0.md +0 -0
  67. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.2.1.md +0 -0
  68. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.2.10.md +0 -0
  69. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.2.2.md +0 -0
  70. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.2.3.md +0 -0
  71. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.2.4.md +0 -0
  72. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.2.5.md +0 -0
  73. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.2.6.md +0 -0
  74. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.2.7.md +0 -0
  75. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.2.8.md +0 -0
  76. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.2.9.md +0 -0
  77. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.3.0.md +0 -0
  78. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.3.1.md +0 -0
  79. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.3.10.md +0 -0
  80. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.3.11.md +0 -0
  81. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.3.2.md +0 -0
  82. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.3.3.md +0 -0
  83. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.3.4.md +0 -0
  84. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.3.5.md +0 -0
  85. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.3.6.md +0 -0
  86. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.3.7.md +0 -0
  87. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.3.8.md +0 -0
  88. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.3.9.md +0 -0
  89. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.4.0.md +0 -0
  90. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.4.1.md +0 -0
  91. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.4.2.md +0 -0
  92. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.4.3.md +0 -0
  93. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.4.4.md +0 -0
  94. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.5.0.md +0 -0
  95. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.5.1.md +0 -0
  96. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.5.2.md +0 -0
  97. {pymetadata-0.5.3 → pymetadata-0.5.5}/release-notes/0.5.3.md +0 -0
  98. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/cache.py +0 -0
  99. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/console.py +0 -0
  100. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/core/__init__.py +0 -0
  101. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/core/creator.py +0 -0
  102. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/core/synonym.py +0 -0
  103. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/core/xref.py +0 -0
  104. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/examples/__init__.py +0 -0
  105. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/examples/cache_path_example.py +0 -0
  106. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/examples/omex_example.py +0 -0
  107. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/examples/results/test_from_files.omex +0 -0
  108. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/examples/results/test_from_omex.omex +0 -0
  109. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/examples/results/testomex/README.md +0 -0
  110. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/examples/results/testomex/manifest.xml +0 -0
  111. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/examples/results/testomex/models/omex_comp.xml +0 -0
  112. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/examples/results/testomex/models/omex_comp_flat.xml +0 -0
  113. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/examples/results/testomex/models/omex_minimal.xml +0 -0
  114. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/examples/test.omex +0 -0
  115. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/identifiers/__init__.py +0 -0
  116. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/identifiers/miriam.py +0 -0
  117. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/log.py +0 -0
  118. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/metadata/__init__.py +0 -0
  119. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/metadata/kisao.py +0 -0
  120. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/metadata/sbo.py +0 -0
  121. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/omex.py +0 -0
  122. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/omex_v2.py +0 -0
  123. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/ontologies/__init__.py +0 -0
  124. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/ontologies/ols.py +0 -0
  125. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/ontologies/ontology.py +0 -0
  126. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/py.typed +0 -0
  127. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/resources/ontologies/README.md +0 -0
  128. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/resources/templates/ontology_enum.pytemplate +0 -0
  129. {pymetadata-0.5.3 → pymetadata-0.5.5}/src/pymetadata/unichem.py +0 -0
  130. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/core/test_creator.py +0 -0
  131. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/data/omex/BIOMD0000000001.omex +0 -0
  132. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/data/omex/CombineArchiveShowCase.omex +0 -0
  133. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/data/omex/CombineArchiveShowCase_manifest.xml +0 -0
  134. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/data/omex/CompModels.omex +0 -0
  135. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/data/omex/CompModels_manifest.xml +0 -0
  136. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/data/omex/iCGB21FR.omex +0 -0
  137. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/test_chebi.py +0 -0
  138. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/test_ols.py +0 -0
  139. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/test_omex.py +0 -0
  140. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/test_ontology.py +0 -0
  141. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/test_registry.py +0 -0
  142. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/test_sbo_kisao.py +0 -0
  143. {pymetadata-0.5.3 → pymetadata-0.5.5}/tests/test_unichem.py +0 -0
  144. {pymetadata-0.5.3 → pymetadata-0.5.5}/tox.ini +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "0.5.3"
2
+ current_version = "0.5.5"
3
3
  commit = true
4
4
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
5
5
  serialize = ["{major}.{minor}.{patch}"]
@@ -1,3 +1,4 @@
1
+ uv.lock
1
2
  .vscode
2
3
  *.pyc
3
4
  *~
@@ -0,0 +1 @@
1
+ 3.13
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymetadata
3
- Version: 0.5.3
3
+ Version: 0.5.5
4
4
  Summary: pymetadata are python utilities for working with metadata.
5
5
  Author-email: Matthias König <konigmatt@googlemail.com>
6
6
  Maintainer-email: Matthias König <konigmatt@googlemail.com>
@@ -27,16 +27,15 @@ Requires-Dist: pydantic>=2.10.4
27
27
  Requires-Dist: requests>=2.32.3
28
28
  Requires-Dist: rich>=13.9.4
29
29
  Requires-Dist: xmltodict>=0.14.2
30
- Requires-Dist: zeep>=4.3.1
31
30
  Provides-Extra: dev
32
- Requires-Dist: bump-my-version>=0.29.0; extra == 'dev'
33
- Requires-Dist: mypy>=1.9.0; extra == 'dev'
31
+ Requires-Dist: bump-my-version>=1.2.4; extra == 'dev'
32
+ Requires-Dist: mypy>=1.18.2; extra == 'dev'
34
33
  Requires-Dist: pre-commit>=4.0.1; extra == 'dev'
35
- Requires-Dist: ruff>=0.8.6; extra == 'dev'
34
+ Requires-Dist: ruff>=0.14.0; extra == 'dev'
36
35
  Provides-Extra: test
37
- Requires-Dist: pytest-cov>=5.0.0; extra == 'test'
38
- Requires-Dist: pytest>=8.1.1; extra == 'test'
39
- Requires-Dist: tox>=4.14.2; extra == 'test'
36
+ Requires-Dist: pytest-cov>=7.0.0; extra == 'test'
37
+ Requires-Dist: pytest>=8.4.2; extra == 'test'
38
+ Requires-Dist: tox>=4.31.0; extra == 'test'
40
39
  Description-Content-Type: text/markdown
41
40
 
42
41
  ![pymetadata logo](https://github.com/matthiaskoenig/pymetadata/raw/develop/docs/images/favicon/pymetadata-100x100-300dpi.png)
@@ -2,7 +2,7 @@
2
2
 
3
3
  Composite annotations are semantic annotations that are comprised of multiple annotation
4
4
  terms linked using standard qualifiers (also known as “relations” or “predicates”)
5
- to indicate the meaning of anannotation.
5
+ to indicate the meaning of annotation.
6
6
  Composite annotations are used when a single knowledge resource term is not available to
7
7
  sufficiently define a model or data element. For model-component annotations,
8
8
  composite annotations have two primary components:
@@ -40,7 +40,6 @@ dependencies = [
40
40
  "lxml>=5.3",
41
41
  "rich>=13.9.4",
42
42
  "requests>=2.32.3",
43
- "zeep>=4.3.1",
44
43
  "pronto>=2.5.8",
45
44
  "fastobo>=0.12.3",
46
45
  "jinja2>=3.1.5",
@@ -50,15 +49,15 @@ dependencies = [
50
49
 
51
50
  [project.optional-dependencies]
52
51
  dev = [
53
- "bump-my-version>=0.29.0",
54
- "ruff>=0.8.6",
52
+ "bump-my-version>=1.2.4",
53
+ "ruff>=0.14.0",
55
54
  "pre-commit>=4.0.1",
56
- "mypy>=1.9.0",
55
+ "mypy>=1.18.2",
57
56
  ]
58
57
  test = [
59
- "tox>=4.14.2",
60
- "pytest>=8.1.1",
61
- "pytest-cov>=5.0.0",
58
+ "tox>=4.31.0",
59
+ "pytest>=8.4.2",
60
+ "pytest-cov>=7.0.0",
62
61
  ]
63
62
 
64
63
  [project_urls]
@@ -0,0 +1,11 @@
1
+ # Release notes for pymetadata 0.5.4
2
+ ![pymetadata](https://github.com/matthiaskoenig/pymetadata/raw/develop/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
+ ## Features & fixes
8
+ - bugfix for annotations with "/"
9
+ - fixing webservices for Chebi 2.0 (#61)
10
+
11
+ Your pymetadata team
@@ -0,0 +1,13 @@
1
+ # Release notes for pymetadata 0.5.5
2
+ ![pymetadata](https://github.com/matthiaskoenig/pymetadata/raw/develop/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
+ ## Features & fixes
8
+ - remove custom namespaces (#62)
9
+ - better support of compact identifiers.org identifiers (#63)
10
+ - support of bioregistry.io as provider (#40)
11
+ - updated ECO ontology
12
+
13
+ Your pymetadata team
@@ -3,7 +3,7 @@
3
3
  from pathlib import Path
4
4
 
5
5
  __author__ = "Matthias Koenig"
6
- __version__ = "0.5.3"
6
+ __version__ = "0.5.5"
7
7
 
8
8
 
9
9
  program_name: str = "pymetadata"
@@ -1,28 +1,19 @@
1
1
  """Module for working with chebi."""
2
2
 
3
3
  from pathlib import Path
4
- from pprint import pprint
5
4
  from typing import Any, Dict, Optional
6
-
7
- from zeep import Client
8
-
5
+ import requests
9
6
 
10
7
  import pymetadata
11
8
  from pymetadata import log
12
9
  from pymetadata.cache import DataclassJSONEncoder, read_json_cache, write_json_cache
10
+ from pymetadata.console import console
13
11
 
14
12
  logger = log.get_logger(__name__)
15
13
 
16
- # FIXME: copy the file to the cache dir
17
- client = Client(str(pymetadata.RESOURCES_DIR / "chebi_webservice_wsdl.xml"))
18
-
19
14
 
20
15
  class ChebiQuery:
21
- """Class to query information from ChEBI.
22
-
23
- An overview over available methods:
24
- python -mzeep https://www.ebi.ac.uk/webservices/chebi/2.0/webservice?wsdl
25
- """
16
+ """Class to query information from ChEBI."""
26
17
 
27
18
  @staticmethod
28
19
  def query(
@@ -52,27 +43,28 @@ class ChebiQuery:
52
43
 
53
44
  # fetch and cache data
54
45
  if not data:
55
- try:
56
- result = client.service.getCompleteEntity(chebi)
57
- # print(result)
58
- except Exception:
46
+ response = requests.get(
47
+ url=f"https://www.ebi.ac.uk/chebi/backend/api/public/compounds/?chebi_ids={chebi}"
48
+ )
49
+ if response.status_code == 200:
50
+ result = response.json()
51
+ else:
59
52
  logger.error(f"CHEBI information could not be retrieved for: {chebi}")
60
53
  return dict()
61
54
 
62
- # parse formula
63
- formula = None
64
- formulae = result["Formulae"]
65
- if formulae:
66
- formula = formulae[0]["data"]
67
-
55
+ result = result[chebi]["data"]
56
+ chemical_data = result["chemical_data"]
57
+ default_structure = result["default_structure"]
68
58
  data = {
69
59
  "chebi": chebi,
70
- "name": result["chebiAsciiName"],
60
+ "name": result["ascii_name"],
71
61
  "definition": result["definition"],
72
- "formula": formula,
73
- "charge": result["charge"],
74
- "mass": result["mass"],
75
- "inchikey": result["inchiKey"],
62
+ "formula": chemical_data["formula"] if chemical_data else None,
63
+ "charge": chemical_data["charge"] if chemical_data else None,
64
+ "mass": chemical_data["mass"] if chemical_data else None,
65
+ "inchikey": default_structure["standard_inchi_key"]
66
+ if default_structure
67
+ else None,
76
68
  }
77
69
 
78
70
  logger.info(f"Write chebi: {chebi_path}")
@@ -86,7 +78,7 @@ class ChebiQuery:
86
78
  if __name__ == "__main__":
87
79
  chebis = ["CHEBI:2668", "CHEBI:138366", "CHEBI:9637", "CHEBI:155897"]
88
80
  for chebi in chebis:
89
- print(chebi)
81
+ console.rule(chebi, align="left", style="bold white")
90
82
  d = ChebiQuery.query(chebi=chebi, cache=False)
91
- pprint(d)
83
+ console.print(d)
92
84
  d = ChebiQuery.query(chebi=chebi, cache=True)
@@ -5,12 +5,14 @@ Core data structure to store annotations.
5
5
 
6
6
  import re
7
7
  import urllib
8
+ from enum import Enum
8
9
  from pprint import pprint
9
10
  from typing import Any, Dict, Final, List, Optional, Tuple, Union
10
11
 
11
12
  import requests
12
13
 
13
14
  from pymetadata import log
15
+ from pymetadata.console import console
14
16
  from pymetadata.core.xref import CrossReference, is_url
15
17
  from pymetadata.identifiers.miriam import BQB, BQM
16
18
  from pymetadata.identifiers.registry import REGISTRY
@@ -19,19 +21,31 @@ from pymetadata.ontologies.ols import ONTOLOGIES, OLSQuery
19
21
 
20
22
  OLS_QUERY = OLSQuery(ontologies=ONTOLOGIES)
21
23
 
22
- IDENTIFIERS_ORG_PREFIX: Final = "http://identifiers.org"
24
+ IDENTIFIERS_ORG_PREFIX: Final = "https://identifiers.org"
23
25
  IDENTIFIERS_ORG_PATTERN1: Final = re.compile(r"^https?://identifiers.org/(.+?)/(.+)")
24
26
  IDENTIFIERS_ORG_PATTERN2: Final = re.compile(r"^https?://identifiers.org/(.+)")
27
+
28
+ BIOREGISTRY_PREFIX: Final = "https://bioregistry.io"
29
+ BIOREGISTRY_PATTERN: Final = re.compile(r"^https?://bioregistry.io/(.+)")
30
+
25
31
  MIRIAM_URN_PATTERN: Final = re.compile(r"^urn:miriam:(.+)")
26
32
 
27
33
  logger = log.get_logger(__name__)
28
34
 
29
35
 
36
+ class ProviderType(str, Enum):
37
+ """Provider type."""
38
+
39
+ IDENTIFIERS_ORG = "identifiers.org"
40
+ BIOREGISTRY_IO = "bioregistry.io"
41
+ NONE = "none"
42
+
43
+
30
44
  class RDFAnnotation:
31
45
  """RDFAnnotation class.
32
46
 
33
47
  Basic storage of annotation information. This consists of the relation
34
- and the the resource.
48
+ and the resource.
35
49
  The annotations can be attached to other objects thereby forming
36
50
  triples which can be converted to RDF.
37
51
 
@@ -40,6 +54,7 @@ class RDFAnnotation:
40
54
  - `collection/term`, i.e., the combination of collection and term
41
55
  - `http(s)://arbitrary.url`, an arbitrary URL
42
56
  - urn:miriam:uniprot:P03023
57
+ - https://bioregistry.io/chebi:15996 urls via the bioregistry provider
43
58
  """
44
59
 
45
60
  replaced_collections: Dict[str, str] = {
@@ -53,6 +68,7 @@ class RDFAnnotation:
53
68
  self.collection: Optional[str] = None
54
69
  self.term: Optional[str] = None
55
70
  self.resource: str = resource
71
+ self.provider: ProviderType = ProviderType.IDENTIFIERS_ORG
56
72
 
57
73
  if not qualifier:
58
74
  raise ValueError(
@@ -75,15 +91,19 @@ class RDFAnnotation:
75
91
  if match1:
76
92
  # handle identifiers.org pattern
77
93
  self.collection, self.term = match1.group(1), match1.group(2)
94
+ self.provider = ProviderType.IDENTIFIERS_ORG
78
95
 
79
96
  if not self.collection:
80
- # tests new short pattern
97
+ # tests new compact patterns
81
98
  match2 = IDENTIFIERS_ORG_PATTERN2.match(resource)
82
99
  if match2:
83
100
  tokens = match2.group(1).split(":")
84
101
  if len(tokens) == 2:
85
102
  self.collection = tokens[0].lower()
103
+
104
+ # check if the namespace is embedded
86
105
  self.term = match2.group(1)
106
+ self.provider = ProviderType.IDENTIFIERS_ORG
87
107
  else:
88
108
  logger.warning(
89
109
  f"Identifiers.org URL does not conform to new"
@@ -94,16 +114,25 @@ class RDFAnnotation:
94
114
  # other urls are directly stored as resources without collection
95
115
  self.collection = None
96
116
  self.term = resource
97
- logger.debug(
98
- f"{resource} does not conform to "
99
- f"http(s)://identifiers.org/collection/id or http(s)://identifiers.org/id",
100
- )
117
+ if BIOREGISTRY_PATTERN.match(resource):
118
+ self.provider = ProviderType.BIOREGISTRY_IO
119
+ console.print(self.provider)
120
+ else:
121
+ self.provider = ProviderType.NONE
122
+ logger.warning(
123
+ f"{resource} does not conform to "
124
+ f"http(s)://identifiers.org/collection/id or http(s)://identifiers.org/id or "
125
+ f"https://bioregistry.io/id .",
126
+ )
127
+
128
+ # handle urns
101
129
  elif resource.startswith("urn:miriam:"):
102
130
  match3 = MIRIAM_URN_PATTERN.match(resource)
103
131
  if match3:
104
132
  tokens = match3.group(1).split(":")
105
133
  self.collection = tokens[0]
106
134
  self.term = ":".join(tokens[1:]).replace("%3A", ":")
135
+ self.provider = ProviderType.IDENTIFIERS_ORG
107
136
 
108
137
  logger.warning(
109
138
  f"Deprecated urn pattern `{resource}` updated: "
@@ -113,13 +142,16 @@ class RDFAnnotation:
113
142
  else:
114
143
  # handle short notation
115
144
  tokens = resource.split("/")
116
- if len(tokens) == 2:
145
+ if len(tokens) > 1:
117
146
  self.collection = tokens[0]
118
147
  self.term = "/".join(tokens[1:])
148
+ self.provider = ProviderType.IDENTIFIERS_ORG
119
149
  elif len(tokens) == 1 and ":" in tokens[0]:
120
150
  self.collection = tokens[0].split(":")[0].lower()
121
151
  self.term = tokens[0]
152
+ self.provider = ProviderType.IDENTIFIERS_ORG
122
153
 
154
+ # validation
123
155
  if len(tokens) < 2 and not self.collection:
124
156
  logger.error(
125
157
  f"Resource `{resource}` could not be split in collection and term. "
@@ -129,6 +161,13 @@ class RDFAnnotation:
129
161
  )
130
162
  self.collection = None
131
163
  self.term = resource
164
+ self.provider = ProviderType.NONE
165
+
166
+ # shorten compact terms
167
+ if self.term and self.collection:
168
+ self.term = self.shorten_compact_term(
169
+ term=self.term, collection=self.collection
170
+ )
132
171
 
133
172
  # clean legacy collections
134
173
  if self.collection in self.replaced_collections:
@@ -136,6 +175,21 @@ class RDFAnnotation:
136
175
 
137
176
  self.validate()
138
177
 
178
+ @staticmethod
179
+ def shorten_compact_term(term: str, collection: str) -> str:
180
+ """Shorten the compact terms and return term.
181
+
182
+ If the namespace is not embeddd in the term return the shortened term.
183
+ """
184
+ namespace = REGISTRY.ns_dict.get(collection, None)
185
+ if namespace and not namespace.namespaceEmbeddedInLui:
186
+ # shorter term
187
+ if term.lower().startswith(collection):
188
+ tokens = term.split(":")
189
+ term = ":".join(tokens[1:])
190
+
191
+ return term
192
+
139
193
  @staticmethod
140
194
  def from_tuple(t: Tuple[Union[BQB, BQM], str]) -> "RDFAnnotation":
141
195
  """Construct from tuple."""
@@ -161,12 +215,12 @@ class RDFAnnotation:
161
215
 
162
216
  def __repr__(self) -> str:
163
217
  """Get representation string."""
164
- return f"RDFAnnotation({self.qualifier}|{self.collection}|{self.term})"
218
+ return f"RDFAnnotation({self.qualifier}|{self.collection}|{self.term}|{self.provider.value})"
165
219
 
166
220
  def to_dict(self) -> Dict:
167
221
  """Convert to dict."""
168
222
  return {
169
- "qualifier": self.qualifier.value, # FIXME use enums!
223
+ "qualifier": self.qualifier.value,
170
224
  "collection": self.collection,
171
225
  "term": self.term,
172
226
  }
@@ -343,52 +397,59 @@ class RDFAnnotationData(RDFAnnotation):
343
397
 
344
398
  if __name__ == "__main__":
345
399
  for annotation in [
346
- # FIXME: support this
347
400
  RDFAnnotation(
348
401
  qualifier=BQB.IS_VERSION_OF,
349
- resource="NCIT:C75913",
350
- ),
351
- RDFAnnotation(
352
- qualifier=BQB.IS_VERSION_OF,
353
- resource="taxonomy/562",
354
- ),
355
- RDFAnnotation(
356
- qualifier=BQB.IS_VERSION_OF,
357
- resource="http://identifiers.org/taxonomy/9606",
358
- ),
359
- RDFAnnotation(
360
- qualifier=BQB.IS_VERSION_OF,
361
- resource="http://identifiers.org/biomodels.sbo/SBO:0000247",
362
- ),
363
- RDFAnnotation(
364
- qualifier=BQB.IS_VERSION_OF, resource="urn:miriam:obo.go:GO%3A0005623"
365
- ),
366
- RDFAnnotation(
367
- qualifier=BQB.IS_VERSION_OF, resource="urn:miriam:chebi:CHEBI%3A33699"
368
- ),
369
- RDFAnnotation(qualifier=BQB.IS_VERSION_OF, resource="chebi/CHEBI:456215"),
370
- RDFAnnotation(
371
- qualifier=BQB.IS, resource="https://en.wikipedia.org/wiki/Cytosol"
372
- ),
373
- RDFAnnotation(
374
- qualifier=BQB.IS_VERSION_OF, resource="urn:miriam:uniprot:P03023"
375
- ),
376
- RDFAnnotation(
377
- qualifier=BQB.IS_VERSION_OF,
378
- resource="http://identifiers.org/go/GO:0005829",
379
- ),
380
- RDFAnnotation(
381
- qualifier=BQB.IS_VERSION_OF, resource="http://identifiers.org/go/GO:0005829"
382
- ),
383
- RDFAnnotation(
384
- qualifier=BQB.IS_VERSION_OF, resource="http://identifiers.org/GO:0005829"
385
- ),
386
- RDFAnnotation(
387
- qualifier=BQB.IS_VERSION_OF, resource="http://identifiers.org/GO:0005829"
402
+ resource="https://bioregistry.io/chebi:15996",
388
403
  ),
389
- RDFAnnotation(qualifier=BQB.IS_VERSION_OF, resource="bto/BTO:0000089"),
390
- RDFAnnotation(qualifier=BQB.IS_VERSION_OF, resource="BTO:0000089"),
391
- RDFAnnotation(qualifier=BQB.IS_VERSION_OF, resource="chebi/CHEBI:000012"),
404
+ # RDFAnnotation(
405
+ # qualifier=BQB.IS_VERSION_OF,
406
+ # resource="NCIT:C75913",
407
+ # ),
408
+ # RDFAnnotation(
409
+ # qualifier=BQB.IS_VERSION_OF,
410
+ # resource="ncit:C75913",
411
+ # ),
412
+ # RDFAnnotation(
413
+ # qualifier=BQB.IS_VERSION_OF,
414
+ # resource="taxonomy/562",
415
+ # ),
416
+ # RDFAnnotation(
417
+ # qualifier=BQB.IS_VERSION_OF,
418
+ # resource="http://identifiers.org/taxonomy/9606",
419
+ # ),
420
+ # RDFAnnotation(
421
+ # qualifier=BQB.IS_VERSION_OF,
422
+ # resource="http://identifiers.org/biomodels.sbo/SBO:0000247",
423
+ # ),
424
+ # RDFAnnotation(
425
+ # qualifier=BQB.IS_VERSION_OF, resource="urn:miriam:obo.go:GO%3A0005623"
426
+ # ),
427
+ # RDFAnnotation(
428
+ # qualifier=BQB.IS_VERSION_OF, resource="urn:miriam:chebi:CHEBI%3A33699"
429
+ # ),
430
+ # RDFAnnotation(qualifier=BQB.IS_VERSION_OF, resource="chebi/CHEBI:456215"),
431
+ # RDFAnnotation(
432
+ # qualifier=BQB.IS, resource="https://en.wikipedia.org/wiki/Cytosol"
433
+ # ),
434
+ # RDFAnnotation(
435
+ # qualifier=BQB.IS_VERSION_OF, resource="urn:miriam:uniprot:P03023"
436
+ # ),
437
+ # RDFAnnotation(
438
+ # qualifier=BQB.IS_VERSION_OF,
439
+ # resource="http://identifiers.org/go/GO:0005829",
440
+ # ),
441
+ # RDFAnnotation(
442
+ # qualifier=BQB.IS_VERSION_OF, resource="http://identifiers.org/go/GO:0005829"
443
+ # ),
444
+ # RDFAnnotation(
445
+ # qualifier=BQB.IS_VERSION_OF, resource="http://identifiers.org/GO:0005829"
446
+ # ),
447
+ # RDFAnnotation(
448
+ # qualifier=BQB.IS_VERSION_OF, resource="http://identifiers.org/GO:0005829"
449
+ # ),
450
+ # RDFAnnotation(qualifier=BQB.IS_VERSION_OF, resource="bto/BTO:0000089"),
451
+ # RDFAnnotation(qualifier=BQB.IS_VERSION_OF, resource="BTO:0000089"),
452
+ # RDFAnnotation(qualifier=BQB.IS_VERSION_OF, resource="chebi/CHEBI:000012"),
392
453
  ]:
393
454
  print("-" * 80)
394
455
  data = RDFAnnotationData(annotation)
@@ -0,0 +1,173 @@
1
+ """
2
+ Helper tools to work with identifiers registry.
3
+
4
+ https://identifiers.org/
5
+ https://docs.identifiers.org/articles/api.html
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import inspect
11
+ import os
12
+ import time
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ import requests
18
+
19
+ import pymetadata
20
+ from pymetadata import log
21
+ from pymetadata.cache import DataclassJSONEncoder, read_json_cache, write_json_cache
22
+ from pymetadata.console import console
23
+
24
+ logger = log.get_logger(__name__)
25
+
26
+
27
+ @dataclass
28
+ class Resource:
29
+ """Resource."""
30
+
31
+ id: Optional[int]
32
+ providerCode: str
33
+ name: str
34
+ urlPattern: str
35
+ mirId: Optional[str] = field(repr=False)
36
+ description: str = field(repr=False)
37
+ official: bool = field(repr=False)
38
+
39
+ sampleId: Optional[str] = field(repr=False)
40
+ resourceHomeUrl: Optional[str] = field(repr=False)
41
+ institution: dict = field(repr=False)
42
+ location: dict = field(repr=False)
43
+ deprecated: bool = field(repr=False)
44
+ deprecationDate: str = field(repr=False)
45
+ protectedUrls: bool = field(repr=False, default=False)
46
+ renderProtectedLanding: bool = field(repr=False, default=False)
47
+ authHelpUrl: Optional[str] = field(repr=False, default=None)
48
+ authHelpDescription: Optional[str] = field(repr=False, default=None)
49
+
50
+ @classmethod
51
+ def from_dict(cls, d: Dict[str, Any]) -> Resource:
52
+ """Handle additional keyword arguments."""
53
+ return cls(
54
+ **{k: v for k, v in d.items() if k in inspect.signature(cls).parameters}
55
+ )
56
+
57
+
58
+ @dataclass
59
+ class Namespace:
60
+ """Namespace."""
61
+
62
+ id: Optional[str]
63
+ prefix: Optional[str]
64
+ name: str
65
+ pattern: str
66
+ namespaceEmbeddedInLui: bool
67
+ description: str = field(repr=False)
68
+ mirId: Optional[str] = field(repr=False, default=None)
69
+ resources: Optional[List] = field(repr=False, default=None)
70
+ created: Optional[str] = field(repr=False, default=None)
71
+ modified: Optional[str] = field(repr=False, default=None)
72
+ sampleId: Optional[str] = field(repr=False, default=None)
73
+ deprecated: bool = field(repr=False, default=False)
74
+ deprecationDate: Optional[str] = field(repr=False, default=None)
75
+
76
+ @classmethod
77
+ def from_dict(cls, d: Dict[str, Any]) -> Namespace:
78
+ """Handle additional keyword arguments."""
79
+ return cls(
80
+ **{k: v for k, v in d.items() if k in inspect.signature(cls).parameters}
81
+ )
82
+
83
+ def __post_init__(self) -> None:
84
+ """Set resources."""
85
+ if self.resources is not None:
86
+ self.resources = [Resource.from_dict(d) for d in self.resources]
87
+ else:
88
+ self.resources = list()
89
+
90
+
91
+ class Registry:
92
+ """Managing the available annotation information.
93
+
94
+ Registry of meta information.
95
+ """
96
+
97
+ URL = "https://registry.api.identifiers.org/resolutionApi/getResolverDataset"
98
+
99
+ def __init__(
100
+ self,
101
+ cache_duration: int = 24,
102
+ cache: bool = True,
103
+ ):
104
+ """Initialize registry.
105
+
106
+ :param cache_path: Path of cached identifiers.org path
107
+ :param cache_duration: Duration of caching in hours.
108
+ :param cache: boolean flag to stop caching
109
+ """
110
+ self.registry_path = pymetadata.CACHE_PATH / "identifiers_registry.json"
111
+
112
+ # check if update needed
113
+ if cache:
114
+ if os.path.exists(self.registry_path):
115
+ registry_age = (
116
+ time.time() - os.path.getmtime(self.registry_path)
117
+ ) / 3600 # [hr]
118
+ update = registry_age > cache_duration
119
+ else:
120
+ update = True
121
+ else:
122
+ update = True
123
+
124
+ self.ns_dict: Dict[str, Namespace] = (
125
+ self.update() if update else Registry.load_registry(self.registry_path)
126
+ )
127
+
128
+ def update(self) -> Dict[str, Namespace]:
129
+ """Update registry."""
130
+ Registry.update_registry(registry_path=self.registry_path)
131
+ return Registry.load_registry(registry_path=self.registry_path)
132
+
133
+ @staticmethod
134
+ def update_registry(
135
+ registry_path: Optional[Path] = None,
136
+ ) -> Dict[str, Namespace]:
137
+ """Update registry from identifiers.org webservice."""
138
+ logger.info(f"Update registry from '{Registry.URL}'")
139
+ response = requests.get(Registry.URL)
140
+ namespaces = response.json()["payload"]["namespaces"]
141
+
142
+ ns_dict = {}
143
+ for _, data in enumerate(namespaces):
144
+ ns = Namespace.from_dict(data)
145
+ ns_dict[ns.prefix] = ns
146
+
147
+ if registry_path is not None:
148
+ write_json_cache(
149
+ data=ns_dict,
150
+ cache_path=registry_path,
151
+ json_encoder=DataclassJSONEncoder,
152
+ )
153
+
154
+ return ns_dict # type: ignore
155
+
156
+ @staticmethod
157
+ def load_registry(registry_path: Path) -> Dict[str, Namespace]:
158
+ """Load namespaces with resources from path."""
159
+ if not registry_path.exists():
160
+ Registry.update_registry(registry_path=registry_path)
161
+
162
+ d = read_json_cache(cache_path=registry_path)
163
+ if not d:
164
+ raise ValueError("Registry could not be loaded from cache.")
165
+
166
+ return {k: Namespace(**v) for k, v in d.items()}
167
+
168
+
169
+ REGISTRY = Registry()
170
+
171
+ if __name__ == "__main__":
172
+ registry = Registry(cache=False)
173
+ console.print(registry.ns_dict)