pyobo 0.11.2__py3-none-any.whl → 0.12.1__py3-none-any.whl

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 (228) hide show
  1. pyobo/.DS_Store +0 -0
  2. pyobo/__init__.py +95 -20
  3. pyobo/__main__.py +0 -0
  4. pyobo/api/__init__.py +81 -10
  5. pyobo/api/alts.py +52 -42
  6. pyobo/api/combine.py +39 -0
  7. pyobo/api/edges.py +68 -0
  8. pyobo/api/hierarchy.py +231 -203
  9. pyobo/api/metadata.py +14 -19
  10. pyobo/api/names.py +207 -127
  11. pyobo/api/properties.py +117 -117
  12. pyobo/api/relations.py +68 -94
  13. pyobo/api/species.py +24 -21
  14. pyobo/api/typedefs.py +11 -11
  15. pyobo/api/utils.py +66 -13
  16. pyobo/api/xrefs.py +107 -114
  17. pyobo/cli/__init__.py +0 -0
  18. pyobo/cli/cli.py +35 -50
  19. pyobo/cli/database.py +210 -160
  20. pyobo/cli/database_utils.py +155 -0
  21. pyobo/cli/lookup.py +163 -195
  22. pyobo/cli/utils.py +19 -6
  23. pyobo/constants.py +102 -3
  24. pyobo/getters.py +209 -191
  25. pyobo/gilda_utils.py +52 -250
  26. pyobo/identifier_utils/__init__.py +33 -0
  27. pyobo/identifier_utils/api.py +305 -0
  28. pyobo/identifier_utils/preprocessing.json +873 -0
  29. pyobo/identifier_utils/preprocessing.py +27 -0
  30. pyobo/identifier_utils/relations/__init__.py +8 -0
  31. pyobo/identifier_utils/relations/api.py +162 -0
  32. pyobo/identifier_utils/relations/data.json +5824 -0
  33. pyobo/identifier_utils/relations/data_owl.json +57 -0
  34. pyobo/identifier_utils/relations/data_rdf.json +1 -0
  35. pyobo/identifier_utils/relations/data_rdfs.json +7 -0
  36. pyobo/mocks.py +9 -6
  37. pyobo/ner/__init__.py +9 -0
  38. pyobo/ner/api.py +72 -0
  39. pyobo/ner/normalizer.py +33 -0
  40. pyobo/obographs.py +48 -40
  41. pyobo/plugins.py +5 -4
  42. pyobo/py.typed +0 -0
  43. pyobo/reader.py +1354 -395
  44. pyobo/reader_utils.py +155 -0
  45. pyobo/resource_utils.py +42 -22
  46. pyobo/resources/__init__.py +0 -0
  47. pyobo/resources/goc.py +75 -0
  48. pyobo/resources/goc.tsv +188 -0
  49. pyobo/resources/ncbitaxon.py +4 -5
  50. pyobo/resources/ncbitaxon.tsv.gz +0 -0
  51. pyobo/resources/ro.py +3 -2
  52. pyobo/resources/ro.tsv +0 -0
  53. pyobo/resources/so.py +0 -0
  54. pyobo/resources/so.tsv +0 -0
  55. pyobo/sources/README.md +12 -8
  56. pyobo/sources/__init__.py +52 -29
  57. pyobo/sources/agrovoc.py +0 -0
  58. pyobo/sources/antibodyregistry.py +11 -12
  59. pyobo/sources/bigg/__init__.py +13 -0
  60. pyobo/sources/bigg/bigg_compartment.py +81 -0
  61. pyobo/sources/bigg/bigg_metabolite.py +229 -0
  62. pyobo/sources/bigg/bigg_model.py +46 -0
  63. pyobo/sources/bigg/bigg_reaction.py +77 -0
  64. pyobo/sources/biogrid.py +1 -2
  65. pyobo/sources/ccle.py +7 -12
  66. pyobo/sources/cgnc.py +9 -6
  67. pyobo/sources/chebi.py +1 -1
  68. pyobo/sources/chembl/__init__.py +9 -0
  69. pyobo/sources/{chembl.py → chembl/chembl_compound.py} +13 -25
  70. pyobo/sources/chembl/chembl_target.py +160 -0
  71. pyobo/sources/civic_gene.py +55 -15
  72. pyobo/sources/clinicaltrials.py +160 -0
  73. pyobo/sources/complexportal.py +24 -24
  74. pyobo/sources/conso.py +14 -22
  75. pyobo/sources/cpt.py +0 -0
  76. pyobo/sources/credit.py +1 -9
  77. pyobo/sources/cvx.py +27 -5
  78. pyobo/sources/depmap.py +9 -12
  79. pyobo/sources/dictybase_gene.py +2 -7
  80. pyobo/sources/drugbank/__init__.py +9 -0
  81. pyobo/sources/{drugbank.py → drugbank/drugbank.py} +11 -16
  82. pyobo/sources/{drugbank_salt.py → drugbank/drugbank_salt.py} +3 -8
  83. pyobo/sources/drugcentral.py +17 -13
  84. pyobo/sources/expasy.py +31 -34
  85. pyobo/sources/famplex.py +13 -18
  86. pyobo/sources/flybase.py +8 -13
  87. pyobo/sources/gard.py +62 -0
  88. pyobo/sources/geonames/__init__.py +9 -0
  89. pyobo/sources/geonames/features.py +28 -0
  90. pyobo/sources/{geonames.py → geonames/geonames.py} +87 -26
  91. pyobo/sources/geonames/utils.py +115 -0
  92. pyobo/sources/gmt_utils.py +6 -7
  93. pyobo/sources/go.py +20 -13
  94. pyobo/sources/gtdb.py +154 -0
  95. pyobo/sources/gwascentral/__init__.py +9 -0
  96. pyobo/sources/{gwascentral_phenotype.py → gwascentral/gwascentral_phenotype.py} +5 -7
  97. pyobo/sources/{gwascentral_study.py → gwascentral/gwascentral_study.py} +1 -7
  98. pyobo/sources/hgnc/__init__.py +9 -0
  99. pyobo/sources/{hgnc.py → hgnc/hgnc.py} +56 -70
  100. pyobo/sources/{hgncgenefamily.py → hgnc/hgncgenefamily.py} +8 -18
  101. pyobo/sources/icd/__init__.py +9 -0
  102. pyobo/sources/{icd10.py → icd/icd10.py} +35 -37
  103. pyobo/sources/icd/icd11.py +148 -0
  104. pyobo/sources/{icd_utils.py → icd/icd_utils.py} +66 -20
  105. pyobo/sources/interpro.py +4 -9
  106. pyobo/sources/itis.py +0 -5
  107. pyobo/sources/kegg/__init__.py +0 -0
  108. pyobo/sources/kegg/api.py +16 -38
  109. pyobo/sources/kegg/genes.py +9 -20
  110. pyobo/sources/kegg/genome.py +1 -7
  111. pyobo/sources/kegg/pathway.py +9 -21
  112. pyobo/sources/mesh.py +58 -24
  113. pyobo/sources/mgi.py +3 -10
  114. pyobo/sources/mirbase/__init__.py +11 -0
  115. pyobo/sources/{mirbase.py → mirbase/mirbase.py} +8 -11
  116. pyobo/sources/{mirbase_constants.py → mirbase/mirbase_constants.py} +0 -0
  117. pyobo/sources/{mirbase_family.py → mirbase/mirbase_family.py} +4 -8
  118. pyobo/sources/{mirbase_mature.py → mirbase/mirbase_mature.py} +3 -7
  119. pyobo/sources/msigdb.py +74 -39
  120. pyobo/sources/ncbi/__init__.py +9 -0
  121. pyobo/sources/ncbi/ncbi_gc.py +162 -0
  122. pyobo/sources/{ncbigene.py → ncbi/ncbigene.py} +18 -19
  123. pyobo/sources/nih_reporter.py +60 -0
  124. pyobo/sources/nlm/__init__.py +9 -0
  125. pyobo/sources/nlm/nlm_catalog.py +48 -0
  126. pyobo/sources/nlm/nlm_publisher.py +36 -0
  127. pyobo/sources/nlm/utils.py +116 -0
  128. pyobo/sources/npass.py +6 -8
  129. pyobo/sources/omim_ps.py +11 -4
  130. pyobo/sources/pathbank.py +4 -8
  131. pyobo/sources/pfam/__init__.py +9 -0
  132. pyobo/sources/{pfam.py → pfam/pfam.py} +3 -8
  133. pyobo/sources/{pfam_clan.py → pfam/pfam_clan.py} +2 -7
  134. pyobo/sources/pharmgkb/__init__.py +15 -0
  135. pyobo/sources/pharmgkb/pharmgkb_chemical.py +89 -0
  136. pyobo/sources/pharmgkb/pharmgkb_disease.py +77 -0
  137. pyobo/sources/pharmgkb/pharmgkb_gene.py +108 -0
  138. pyobo/sources/pharmgkb/pharmgkb_pathway.py +63 -0
  139. pyobo/sources/pharmgkb/pharmgkb_variant.py +84 -0
  140. pyobo/sources/pharmgkb/utils.py +86 -0
  141. pyobo/sources/pid.py +1 -6
  142. pyobo/sources/pombase.py +6 -10
  143. pyobo/sources/pubchem.py +4 -9
  144. pyobo/sources/reactome.py +5 -11
  145. pyobo/sources/rgd.py +11 -16
  146. pyobo/sources/rhea.py +37 -36
  147. pyobo/sources/ror.py +69 -42
  148. pyobo/sources/selventa/__init__.py +0 -0
  149. pyobo/sources/selventa/schem.py +4 -7
  150. pyobo/sources/selventa/scomp.py +1 -6
  151. pyobo/sources/selventa/sdis.py +4 -7
  152. pyobo/sources/selventa/sfam.py +1 -6
  153. pyobo/sources/sgd.py +6 -11
  154. pyobo/sources/signor/__init__.py +7 -0
  155. pyobo/sources/signor/download.py +41 -0
  156. pyobo/sources/signor/signor_complexes.py +105 -0
  157. pyobo/sources/slm.py +12 -15
  158. pyobo/sources/umls/__init__.py +7 -1
  159. pyobo/sources/umls/__main__.py +0 -0
  160. pyobo/sources/umls/get_synonym_types.py +20 -4
  161. pyobo/sources/umls/sty.py +57 -0
  162. pyobo/sources/umls/synonym_types.tsv +1 -1
  163. pyobo/sources/umls/umls.py +18 -22
  164. pyobo/sources/unimod.py +46 -0
  165. pyobo/sources/uniprot/__init__.py +1 -1
  166. pyobo/sources/uniprot/uniprot.py +40 -32
  167. pyobo/sources/uniprot/uniprot_ptm.py +4 -34
  168. pyobo/sources/utils.py +3 -2
  169. pyobo/sources/wikipathways.py +7 -10
  170. pyobo/sources/zfin.py +5 -10
  171. pyobo/ssg/__init__.py +12 -16
  172. pyobo/ssg/base.html +0 -0
  173. pyobo/ssg/index.html +26 -13
  174. pyobo/ssg/term.html +12 -2
  175. pyobo/ssg/typedef.html +0 -0
  176. pyobo/struct/__init__.py +54 -8
  177. pyobo/struct/functional/__init__.py +1 -0
  178. pyobo/struct/functional/dsl.py +2572 -0
  179. pyobo/struct/functional/macros.py +423 -0
  180. pyobo/struct/functional/obo_to_functional.py +385 -0
  181. pyobo/struct/functional/ontology.py +272 -0
  182. pyobo/struct/functional/utils.py +112 -0
  183. pyobo/struct/reference.py +331 -136
  184. pyobo/struct/struct.py +1484 -657
  185. pyobo/struct/struct_utils.py +1078 -0
  186. pyobo/struct/typedef.py +162 -210
  187. pyobo/struct/utils.py +12 -5
  188. pyobo/struct/vocabulary.py +138 -0
  189. pyobo/utils/__init__.py +0 -0
  190. pyobo/utils/cache.py +16 -15
  191. pyobo/utils/io.py +51 -41
  192. pyobo/utils/iter.py +5 -5
  193. pyobo/utils/misc.py +41 -53
  194. pyobo/utils/ndex_utils.py +0 -0
  195. pyobo/utils/path.py +73 -70
  196. pyobo/version.py +3 -3
  197. pyobo-0.12.1.dist-info/METADATA +671 -0
  198. pyobo-0.12.1.dist-info/RECORD +201 -0
  199. pyobo-0.12.1.dist-info/WHEEL +4 -0
  200. {pyobo-0.11.2.dist-info → pyobo-0.12.1.dist-info}/entry_points.txt +1 -0
  201. pyobo-0.12.1.dist-info/licenses/LICENSE +21 -0
  202. pyobo/aws.py +0 -162
  203. pyobo/cli/aws.py +0 -47
  204. pyobo/identifier_utils.py +0 -142
  205. pyobo/normalizer.py +0 -232
  206. pyobo/registries/__init__.py +0 -16
  207. pyobo/registries/metaregistry.json +0 -507
  208. pyobo/registries/metaregistry.py +0 -135
  209. pyobo/sources/icd11.py +0 -105
  210. pyobo/xrefdb/__init__.py +0 -1
  211. pyobo/xrefdb/canonicalizer.py +0 -214
  212. pyobo/xrefdb/priority.py +0 -59
  213. pyobo/xrefdb/sources/__init__.py +0 -60
  214. pyobo/xrefdb/sources/biomappings.py +0 -36
  215. pyobo/xrefdb/sources/cbms2019.py +0 -91
  216. pyobo/xrefdb/sources/chembl.py +0 -83
  217. pyobo/xrefdb/sources/compath.py +0 -82
  218. pyobo/xrefdb/sources/famplex.py +0 -64
  219. pyobo/xrefdb/sources/gilda.py +0 -50
  220. pyobo/xrefdb/sources/intact.py +0 -113
  221. pyobo/xrefdb/sources/ncit.py +0 -133
  222. pyobo/xrefdb/sources/pubchem.py +0 -27
  223. pyobo/xrefdb/sources/wikidata.py +0 -116
  224. pyobo/xrefdb/xrefs_pipeline.py +0 -180
  225. pyobo-0.11.2.dist-info/METADATA +0 -711
  226. pyobo-0.11.2.dist-info/RECORD +0 -157
  227. pyobo-0.11.2.dist-info/WHEEL +0 -5
  228. pyobo-0.11.2.dist-info/top_level.txt +0 -1
pyobo/cli/lookup.py CHANGED
@@ -2,17 +2,20 @@
2
2
 
3
3
  import json
4
4
  import sys
5
- from typing import Optional
5
+ from collections.abc import Mapping
6
6
 
7
7
  import bioregistry
8
8
  import click
9
9
  from more_click import verbose_option
10
+ from typing_extensions import Unpack
10
11
 
11
12
  from .utils import (
13
+ Clickable,
12
14
  echo_df,
13
15
  force_option,
14
- no_strict_option,
16
+ force_process_option,
15
17
  prefix_argument,
18
+ strict_option,
16
19
  version_option,
17
20
  )
18
21
  from ..api import (
@@ -28,15 +31,17 @@ from ..api import (
28
31
  get_id_synonyms_mapping,
29
32
  get_id_to_alts,
30
33
  get_ids,
34
+ get_mappings_df,
31
35
  get_metadata,
32
36
  get_name,
33
- get_name_by_curie,
34
37
  get_properties_df,
35
38
  get_relations_df,
36
39
  get_typedef_df,
37
40
  get_xrefs_df,
38
41
  )
39
- from ..identifier_utils import normalize_curie
42
+ from ..constants import LookupKwargs
43
+ from ..getters import get_ontology
44
+ from ..struct.reference import _parse_str_or_curie_or_uri
40
45
 
41
46
  __all__ = [
42
47
  "lookup",
@@ -48,150 +53,123 @@ def lookup():
48
53
  """Lookup resources."""
49
54
 
50
55
 
51
- @lookup.command()
52
- @prefix_argument
56
+ def lookup_annotate(f: Clickable) -> Clickable:
57
+ """Add appropriate decorators to lookup CLI functions."""
58
+ for decorator in [
59
+ lookup.command(),
60
+ prefix_argument,
61
+ verbose_option,
62
+ force_option,
63
+ force_process_option,
64
+ strict_option,
65
+ version_option,
66
+ ]:
67
+ f = decorator(f)
68
+ return f
69
+
70
+
71
+ identifier_option = click.option("-i", "--identifier")
72
+
73
+
74
+ @lookup_annotate
53
75
  @click.option("-t", "--target")
54
- @verbose_option
55
- @force_option
56
- @no_strict_option
57
- @version_option
58
- def xrefs(prefix: str, target: str, force: bool, no_strict: bool, version: Optional[str]):
76
+ def xrefs(target: str, **kwargs: Unpack[LookupKwargs]) -> None:
59
77
  """Page through xrefs for the given namespace to the second given namespace."""
60
78
  if target:
61
- target = bioregistry.normalize_prefix(target)
62
- filtered_xrefs = get_filtered_xrefs(
63
- prefix, target, force=force, strict=not no_strict, version=version
64
- )
79
+ target_norm = bioregistry.normalize_prefix(target)
80
+ filtered_xrefs = get_filtered_xrefs(xref_prefix=target_norm, **kwargs)
65
81
  click.echo_via_pager(
66
82
  "\n".join(f"{identifier}\t{_xref}" for identifier, _xref in filtered_xrefs.items())
67
83
  )
68
84
  else:
69
- all_xrefs_df = get_xrefs_df(prefix, force=force, strict=not no_strict, version=version)
85
+ all_xrefs_df = get_xrefs_df(**kwargs)
70
86
  echo_df(all_xrefs_df)
71
87
 
72
88
 
73
- @lookup.command()
74
- @prefix_argument
75
- @verbose_option
76
- @force_option
77
- @version_option
78
- def metadata(prefix: str, force: bool, version: Optional[str]):
89
+ @lookup_annotate
90
+ @click.option("--include-names", is_flag=True)
91
+ @click.option("-t", "--target")
92
+ def mappings(include_names: bool, target: str | None, **kwargs: Unpack[LookupKwargs]) -> None:
93
+ """Page through mappings for the given namespace."""
94
+ mappings_df = get_mappings_df(names=include_names, **kwargs)
95
+ if target:
96
+ target_norm = bioregistry.normalize_prefix(target)
97
+ if target_norm is None:
98
+ raise ValueError
99
+ idx = mappings_df["object_id"].map(
100
+ lambda x: bioregistry.normalize_prefix(x.split(":")[0]) == target_norm
101
+ )
102
+ mappings_df = mappings_df[idx]
103
+ echo_df(mappings_df)
104
+
105
+
106
+ @lookup_annotate
107
+ def metadata(**kwargs: Unpack[LookupKwargs]) -> None:
79
108
  """Print the metadata for the given namespace."""
80
- metadata = get_metadata(prefix, force=force, version=version)
109
+ metadata = get_metadata(**kwargs)
81
110
  click.echo(json.dumps(metadata, indent=2))
82
111
 
83
112
 
84
- @lookup.command()
85
- @prefix_argument
86
- @verbose_option
87
- @force_option
88
- @no_strict_option
89
- @version_option
90
- def ids(prefix: str, force: bool, no_strict: bool, version: Optional[str]):
113
+ @lookup_annotate
114
+ def ids(**kwargs: Unpack[LookupKwargs]) -> None:
91
115
  """Page through the identifiers of entities in the given namespace."""
92
- id_list = get_ids(prefix, force=force, strict=not no_strict, version=version)
93
- click.echo_via_pager("\n".join(id_list))
94
-
95
-
96
- @lookup.command()
97
- @prefix_argument
98
- @verbose_option
99
- @force_option
100
- @no_strict_option
101
- @click.option("-i", "--identifier")
102
- @version_option
103
- def names(
104
- prefix: str, identifier: Optional[str], force: bool, no_strict: bool, version: Optional[str]
105
- ):
106
- """Page through the identifiers and names of entities in the given namespace."""
107
- id_to_name = get_id_name_mapping(prefix, force=force, strict=not no_strict, version=version)
108
- if identifier is None:
109
- _help_page_mapping(id_to_name)
116
+ id_list = get_ids(**kwargs)
117
+ if not id_list:
118
+ click.secho("no data", fg="red")
110
119
  else:
111
- name = id_to_name.get(identifier)
112
- if name is None:
113
- click.secho(f"No name available for {identifier}", fg="red")
114
- else:
115
- click.echo(name)
116
-
117
-
118
- @lookup.command()
119
- @prefix_argument
120
- @verbose_option
121
- @force_option
122
- @no_strict_option
123
- @click.option("-i", "--identifier")
124
- @version_option
125
- def species(
126
- prefix: str, identifier: Optional[str], force: bool, no_strict: bool, version: Optional[str]
127
- ):
120
+ click.echo_via_pager("\n".join(id_list))
121
+
122
+
123
+ @lookup_annotate
124
+ @identifier_option
125
+ def names(identifier: str | None, **kwargs: Unpack[LookupKwargs]) -> None:
126
+ """Page through the identifiers and names of entities in the given namespace."""
127
+ id_to_name = get_id_name_mapping(**kwargs)
128
+ _help_page_mapping(id_to_name, identifier=identifier)
129
+
130
+
131
+ @lookup_annotate
132
+ @identifier_option
133
+ def species(identifier: str | None, **kwargs: Unpack[LookupKwargs]) -> None:
128
134
  """Page through the identifiers and species of entities in the given namespace."""
129
- id_to_species = get_id_species_mapping(
130
- prefix, force=force, strict=not no_strict, version=version
131
- )
132
- if identifier is None:
133
- _help_page_mapping(id_to_species)
134
- else:
135
- species = id_to_species.get(identifier)
136
- if species is None:
137
- click.secho(f"No species available for {identifier}", fg="red")
138
- else:
139
- click.echo(species)
135
+ id_to_species = get_id_species_mapping(**kwargs)
136
+ _help_page_mapping(id_to_species, identifier=identifier)
140
137
 
141
138
 
142
- @lookup.command()
143
- @prefix_argument
144
- @verbose_option
145
- @force_option
146
- @click.option("-i", "--identifier")
147
- @version_option
148
- def definitions(prefix: str, identifier: Optional[str], force: bool, version: Optional[str]):
139
+ @lookup_annotate
140
+ @identifier_option
141
+ def definitions(identifier: str | None, **kwargs: Unpack[LookupKwargs]) -> None:
149
142
  """Page through the identifiers and definitions of entities in the given namespace."""
150
- id_to_definition = get_id_definition_mapping(prefix, force=force, version=version)
151
- if identifier is None:
152
- _help_page_mapping(id_to_definition)
153
- else:
154
- definition = id_to_definition.get(identifier)
155
- if definition is None:
156
- click.secho(f"No definition available for {identifier}", fg="red")
157
- else:
158
- click.echo(definition)
143
+ id_to_definition = get_id_definition_mapping(**kwargs)
144
+ _help_page_mapping(id_to_definition, identifier=identifier)
159
145
 
160
146
 
161
- @lookup.command()
162
- @prefix_argument
163
- @verbose_option
164
- @force_option
165
- @version_option
166
- def typedefs(prefix: str, force: bool, version: Optional[str]):
147
+ @lookup_annotate
148
+ def typedefs(**kwargs: Unpack[LookupKwargs]) -> None:
167
149
  """Page through the identifiers and names of typedefs in the given namespace."""
168
- df = get_typedef_df(prefix, force=force, version=version)
150
+ df = get_typedef_df(**kwargs)
169
151
  echo_df(df)
170
152
 
171
153
 
172
- def _help_page_mapping(id_to_name):
173
- click.echo_via_pager("\n".join("\t".join(item) for item in id_to_name.items()))
154
+ def _help_page_mapping(id_to_name: Mapping[str, str], *, identifier: str | None = None) -> None:
155
+ if not id_to_name:
156
+ click.secho("no data", fg="red")
157
+ elif identifier:
158
+ value = id_to_name.get(identifier)
159
+ if value:
160
+ click.echo(value)
161
+ else:
162
+ click.secho(f"no data for {identifier}", fg="red")
163
+ else:
164
+ click.echo_via_pager("\n".join("\t".join(item) for item in id_to_name.items()))
174
165
 
175
166
 
176
- @lookup.command()
177
- @prefix_argument
178
- @verbose_option
179
- @force_option
180
- @version_option
181
- def synonyms(prefix: str, force: bool, version: Optional[str]):
167
+ @lookup_annotate
168
+ @identifier_option
169
+ def synonyms(identifier: str | None, **kwargs: Unpack[LookupKwargs]) -> None:
182
170
  """Page through the synonyms for entities in the given namespace."""
183
- if ":" in prefix:
184
- _prefix, identifier = normalize_curie(prefix)
185
- if _prefix is None or identifier is None:
186
- click.secho(f"could not normalize {prefix}")
187
- return sys.exit(1)
188
- name = get_name(_prefix, identifier)
189
- id_to_synonyms = get_id_synonyms_mapping(_prefix, force=force)
190
- click.echo(f"Synonyms for {_prefix}:{identifier} ! {name}")
191
- for synonym in id_to_synonyms.get(identifier, []):
192
- click.echo(synonym)
193
- else: # it's a prefix
194
- id_to_synonyms = get_id_synonyms_mapping(prefix, force=force, version=version)
171
+ id_to_synonyms = get_id_synonyms_mapping(**kwargs)
172
+ if identifier is None:
195
173
  click.echo_via_pager(
196
174
  "\n".join(
197
175
  f"{identifier}\t{_synonym}"
@@ -199,136 +177,126 @@ def synonyms(prefix: str, force: bool, version: Optional[str]):
199
177
  for _synonym in _synonyms
200
178
  )
201
179
  )
180
+ else:
181
+ synonyms = id_to_synonyms.get(identifier, [])
182
+ if not synonyms:
183
+ click.secho(f"No synonyms available for {identifier}", fg="red")
184
+ else:
185
+ click.echo_via_pager("\n".join(synonyms))
202
186
 
203
187
 
204
- @lookup.command()
205
- @prefix_argument
188
+ @lookup_annotate
206
189
  @click.option(
207
190
  "--relation", help="CURIE for the relationship or just the ID if local to the ontology"
208
191
  )
209
192
  @click.option("--target", help="Prefix for the target")
210
- @verbose_option
211
- @no_strict_option
212
- @force_option
213
193
  @click.option("--summarize", is_flag=True)
214
- @version_option
215
194
  def relations(
216
- prefix: str,
217
195
  relation: str,
218
196
  target: str,
219
- force: bool,
220
- no_strict: bool,
221
197
  summarize: bool,
222
- version: Optional[str],
223
- ):
198
+ **kwargs: Unpack[LookupKwargs],
199
+ ) -> None:
224
200
  """Page through the relations for entities in the given namespace."""
225
201
  if relation is None:
226
- relations_df = get_relations_df(prefix, force=force, strict=not no_strict, version=version)
202
+ relations_df = get_relations_df(**kwargs)
227
203
  if summarize:
228
204
  click.echo(relations_df[relations_df.columns[2]].value_counts())
229
205
  else:
230
206
  echo_df(relations_df)
231
207
  else:
232
- curie = normalize_curie(relation)
233
- if curie[1] is None: # that's the identifier
234
- click.secho(f"not valid curie, assuming local to {prefix}", fg="yellow")
235
- curie = prefix, relation
208
+ relation_reference = _parse_str_or_curie_or_uri(relation, strict=False)
209
+ if relation_reference is None:
210
+ click.secho(f"not a valid curie: {relation}", fg="red")
211
+ raise sys.exit(1)
236
212
 
237
213
  if target is not None:
238
214
  norm_target = bioregistry.normalize_prefix(target)
239
215
  if norm_target is None:
240
216
  raise ValueError
241
217
  relations_df = get_filtered_relations_df(
242
- prefix, relation=curie, force=force, strict=not no_strict, target=norm_target
218
+ relation=relation_reference,
219
+ target=norm_target,
220
+ **kwargs,
243
221
  )
244
222
  else:
245
223
  raise NotImplementedError(f"can not filter by target prefix {target}")
246
224
 
247
225
 
248
- @lookup.command()
249
- @prefix_argument
226
+ @lookup_annotate
250
227
  @click.option("--include-part-of", is_flag=True)
251
228
  @click.option("--include-has-member", is_flag=True)
252
- @verbose_option
253
- @force_option
254
- @version_option
255
229
  def hierarchy(
256
- prefix: str,
257
230
  include_part_of: bool,
258
231
  include_has_member: bool,
259
- force: bool,
260
- version: Optional[str],
261
- ):
232
+ **kwargs: Unpack[LookupKwargs],
233
+ ) -> None:
262
234
  """Page through the hierarchy for entities in the namespace."""
263
235
  h = get_hierarchy(
264
- prefix,
265
236
  include_part_of=include_part_of,
266
237
  include_has_member=include_has_member,
267
- force=force,
268
- version=version,
238
+ **kwargs,
269
239
  )
270
- click.echo_via_pager("\n".join("\t".join(row) for row in h.edges()))
240
+ if h.number_of_edges() == 0:
241
+ click.secho("no data", fg="red")
242
+ else:
243
+ click.echo_via_pager("\n".join(f"{u.curie}\t{v.curie}" for u, v in h.edges()))
271
244
 
272
245
 
273
- @lookup.command()
274
- @prefix_argument
246
+ @lookup_annotate
275
247
  @click.argument("identifier")
276
- @verbose_option
277
- @force_option
278
- @version_option
279
- def ancestors(prefix: str, identifier: str, force: bool, version: Optional[str]):
248
+ def ancestors(
249
+ identifier: str,
250
+ **kwargs: Unpack[LookupKwargs],
251
+ ) -> None:
280
252
  """Look up ancestors."""
281
- curies = get_ancestors(prefix=prefix, identifier=identifier, force=force, version=version)
282
- for curie in sorted(curies or []):
283
- click.echo(f"{curie}\t{get_name_by_curie(curie, version=version)}")
253
+ # note, prefix is passed via kwargs
254
+ ancestors = get_ancestors(identifier=identifier, **kwargs)
255
+ for ancestor in sorted(ancestors or []):
256
+ click.echo(f"{ancestor.curie}\t{get_name(ancestor, version=kwargs['version'])}")
284
257
 
285
258
 
286
- @lookup.command()
287
- @prefix_argument
259
+ @lookup_annotate
288
260
  @click.argument("identifier")
289
- @verbose_option
290
- @force_option
291
- @version_option
292
- def descendants(prefix: str, identifier: str, force: bool, version: Optional[str]):
261
+ def descendants(
262
+ identifier: str,
263
+ **kwargs: Unpack[LookupKwargs],
264
+ ) -> None:
293
265
  """Look up descendants."""
294
- curies = get_descendants(prefix=prefix, identifier=identifier, force=force, version=version)
295
- for curie in sorted(curies or []):
296
- click.echo(f"{curie}\t{get_name_by_curie(curie, version=version)}")
266
+ # note, prefix is passed via kwargs
267
+ descendants = get_descendants(identifier=identifier, **kwargs)
268
+ for descendant in sorted(descendants or []):
269
+ click.echo(f"{descendant.curie}\t{get_name(descendant, version=kwargs['version'])}")
297
270
 
298
271
 
299
- @lookup.command()
300
- @prefix_argument
272
+ @lookup_annotate
301
273
  @click.option("-k", "--key")
302
- @verbose_option
303
- @force_option
304
- @version_option
305
- def properties(prefix: str, key: Optional[str], force: bool, version: Optional[str]):
274
+ def properties(
275
+ key: str | None,
276
+ **kwargs: Unpack[LookupKwargs],
277
+ ) -> None:
306
278
  """Page through the properties for entities in the given namespace."""
307
279
  if key is None:
308
- properties_df = get_properties_df(prefix, force=force, version=version)
280
+ properties_df = get_properties_df(**kwargs)
309
281
  else:
310
- properties_df = get_filtered_properties_df(prefix, prop=key, force=force)
282
+ properties_df = get_filtered_properties_df(prop=key, **kwargs)
311
283
  echo_df(properties_df)
312
284
 
313
285
 
314
- @lookup.command()
315
- @prefix_argument
316
- @verbose_option
317
- @force_option
318
- @click.option("-i", "--identifier")
319
- @version_option
320
- def alts(prefix: str, identifier: Optional[str], force: bool, version: Optional[str]):
286
+ @lookup_annotate
287
+ @identifier_option
288
+ def alts(
289
+ identifier: str | None,
290
+ **kwargs: Unpack[LookupKwargs],
291
+ ) -> None:
321
292
  """Page through alt ids in a namespace."""
322
- id_to_alts = get_id_to_alts(prefix, force=force, version=version)
323
- if identifier is None:
324
- click.echo_via_pager(
325
- "\n".join(
326
- f"{identifier}\t{alt}" for identifier, alts in id_to_alts.items() for alt in alts
327
- )
328
- )
329
- else:
330
- _alts = id_to_alts.get(identifier)
331
- if _alts is None:
332
- click.secho(f"No alternate identifiers for {identifier}", fg="red")
333
- else:
334
- click.echo("\n".join(_alts))
293
+ id_to_alts = get_id_to_alts(**kwargs)
294
+ _help_page_mapping(id_to_alts, identifier=identifier)
295
+
296
+
297
+ @lookup_annotate
298
+ def prefixes(**kwargs: Unpack[LookupKwargs]) -> None:
299
+ """Page through prefixes appearing in an ontology."""
300
+ ontology = get_ontology(**kwargs)
301
+ for prefix in sorted(ontology._get_prefixes(), key=str.casefold):
302
+ click.echo(prefix)
pyobo/cli/utils.py CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  import datetime
4
4
  import pathlib
5
+ from collections.abc import Callable
6
+ from typing import TypeVar
5
7
 
6
8
  import click
7
9
  import pandas as pd
@@ -9,12 +11,14 @@ import pandas as pd
9
11
  from ..constants import DATABASE_DIRECTORY
10
12
 
11
13
  __all__ = [
12
- "echo_df",
14
+ "Clickable",
13
15
  "directory_option",
14
- "zenodo_option",
16
+ "echo_df",
15
17
  "force_option",
18
+ "force_process_option",
16
19
  "prefix_argument",
17
- "no_strict_option",
20
+ "strict_option",
21
+ "zenodo_option",
18
22
  ]
19
23
 
20
24
 
@@ -37,12 +41,21 @@ directory_option = click.option(
37
41
  help=f"Build location. Defaults to {DATABASE_DIRECTORY}/<today>",
38
42
  )
39
43
  zenodo_option = click.option("--zenodo", is_flag=True, help="Automatically upload to zenodo")
40
- no_strict_option = click.option(
41
- "-x", "--no-strict", is_flag=True, help="Turn off failure on bad CURIEs"
44
+ strict_option = click.option(
45
+ "--strict/--no-strict",
46
+ default=False,
47
+ show_default=True,
48
+ help="Turn on or off failure on unparsable CURIEs",
42
49
  )
43
50
  prefix_argument = click.argument("prefix")
44
- force_option = click.option("-f", "--force", is_flag=True)
51
+ force_option = click.option(
52
+ "-f", "--force", is_flag=True, help="Force re-downloading and re-processing"
53
+ )
45
54
  version_option = click.option(
46
55
  "--version",
47
56
  help="Explicit version of the data. If not given, the most recent will be looked up.",
48
57
  )
58
+ force_process_option = click.option(
59
+ "--force-process", is_flag=True, help="Force re-processing, but not necessarily re-downloading"
60
+ )
61
+ Clickable = TypeVar("Clickable", bound=Callable)
pyobo/constants.py CHANGED
@@ -1,13 +1,16 @@
1
1
  """Constants for PyOBO."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import logging
4
6
  import re
5
7
 
6
8
  import pystow
9
+ from typing_extensions import NotRequired, TypedDict
7
10
 
8
11
  __all__ = [
9
- "RAW_DIRECTORY",
10
12
  "DATABASE_DIRECTORY",
13
+ "RAW_DIRECTORY",
11
14
  "SPECIES_REMAPPING",
12
15
  ]
13
16
 
@@ -19,6 +22,15 @@ RAW_DIRECTORY = RAW_MODULE.base
19
22
  DATABASE_MODULE = PYOBO_MODULE.module("database")
20
23
  DATABASE_DIRECTORY = DATABASE_MODULE.base
21
24
 
25
+ #: The directory inside an ontology cache where
26
+ #: large artifacts like OBO, OWL, JSON, etc. go
27
+ BUILD_SUBDIRECTORY_NAME = "build"
28
+ #: The directory inside an ontology cache where
29
+ #: small caches for alts, xrefs, names, etc. go
30
+ CACHE_SUBDIRECTORY_NAME = "cache"
31
+ #: the directory for caching relations
32
+ RELATION_SUBDIRECTORY_NAME = "relations"
33
+
22
34
  SPECIES_REMAPPING = {
23
35
  "Canis familiaris": "Canis lupus familiaris",
24
36
  }
@@ -29,7 +41,6 @@ GLOBAL_SKIP = {
29
41
  "resid", # deprecated
30
42
  "adw", # deprecated
31
43
  }
32
- GLOBAL_CHECK_IDS = False
33
44
 
34
45
  #: Default prefix
35
46
  DEFAULT_PREFIX = "debio"
@@ -79,7 +90,7 @@ TYPEDEFS_FILE = "typedefs.tsv.gz"
79
90
  SPECIES_RECORD = "5334738"
80
91
  SPECIES_FILE = "species.tsv.gz"
81
92
 
82
- NCBITAXON_PREFIX = "NCBITaxon"
93
+ NCBITAXON_PREFIX = "ncbitaxon"
83
94
  DATE_FORMAT = "%d:%m:%Y %H:%M"
84
95
  PROVENANCE_PREFIXES = {
85
96
  "pubmed",
@@ -97,3 +108,91 @@ PROVENANCE_PREFIXES = {
97
108
  "isbn",
98
109
  "issn",
99
110
  }
111
+
112
+
113
+ class DatabaseKwargs(TypedDict):
114
+ """Keyword arguments for database CLI functions."""
115
+
116
+ strict: bool
117
+ force: bool
118
+ force_process: bool
119
+ skip_pyobo: bool
120
+ skip_below: str | None
121
+ skip_set: set[str] | None
122
+ use_tqdm: bool
123
+
124
+
125
+ class SlimGetOntologyKwargs(TypedDict):
126
+ """Keyword arguments for database CLI functions.
127
+
128
+ These arguments are global during iteration over _all_ ontologies, whereas the
129
+ additional ``version`` is added in the subclass below for specific instances when
130
+ only a single ontology is requested.
131
+ """
132
+
133
+ strict: NotRequired[bool]
134
+ force: NotRequired[bool]
135
+ force_process: NotRequired[bool]
136
+
137
+
138
+ class GetOntologyKwargs(SlimGetOntologyKwargs):
139
+ """Represents the optional keyword arguments passed to :func:`pyobo.get_ontology`.
140
+
141
+ This dictionary doesn't contain ``prefix`` since this is always explicitly handled.
142
+ """
143
+
144
+ version: NotRequired[str | None]
145
+ cache: NotRequired[bool]
146
+ use_tqdm: NotRequired[bool]
147
+
148
+
149
+ def check_should_force(data: GetOntologyKwargs) -> bool:
150
+ """Determine whether caching should be forced based on generic keyword arguments."""
151
+ # note that this could be applied to the superclass of GetOntologyKwargs
152
+ # but this function should only be used in the scope where GetOntologyKwargs
153
+ # is appropriate.
154
+ return data.get("force", False) or data.get("force_process", False)
155
+
156
+
157
+ def check_should_cache(data: GetOntologyKwargs) -> bool:
158
+ """Determine whether caching should be done based on generic keyword arguments."""
159
+ return data.get("cache", True)
160
+
161
+
162
+ def check_should_use_tqdm(data: GetOntologyKwargs) -> bool:
163
+ """Determine whether caching should be done based on generic keyword arguments."""
164
+ return data.get("use_tqdm", True)
165
+
166
+
167
+ class LookupKwargs(GetOntologyKwargs):
168
+ """Represents all arguments passed to :func:`pyobo.get_ontology`.
169
+
170
+ This dictionary does contain the ``prefix`` since it's used in the scope of CLI
171
+ functions.
172
+ """
173
+
174
+ prefix: str
175
+
176
+
177
+ class IterHelperHelperDict(SlimGetOntologyKwargs):
178
+ """Represents arguments needed when iterating over all ontologies.
179
+
180
+ The explicitly defind arguments in this typed dict are used for the loop function
181
+ :func:`iter_helper_helper` and the rest that are inherited get passed to
182
+ :func:`pyobo.get_ontology` in each iteration.
183
+ """
184
+
185
+ use_tqdm: bool
186
+ skip_below: str | None
187
+ skip_pyobo: bool
188
+ skip_set: set[str] | None
189
+
190
+
191
+ #: from table 2 of the Functional OWL syntax definition
192
+ #: at https://www.w3.org/TR/owl2-syntax/#IRIs
193
+ DEFAULT_PREFIX_MAP = {
194
+ "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
195
+ "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
196
+ "xsd": "http://www.w3.org/2001/XMLSchema#",
197
+ "owl": "http://www.w3.org/2002/07/owl#",
198
+ }