pyobo 0.12.14__py3-none-any.whl → 0.12.16__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.
pyobo/api/xrefs.py CHANGED
@@ -124,7 +124,11 @@ def get_semantic_mappings(
124
124
  )
125
125
  if converter is None:
126
126
  converter = get_converter()
127
- return [row_to_semantic_mapping(row, converter=converter) for _, row in df.iterrows()]
127
+ return [
128
+ # TODO upstream this into sssom-pydantic?
129
+ row_to_semantic_mapping({k: v for k, v in row.items() if pd.notna(v)}, converter=converter)
130
+ for _, row in df.iterrows()
131
+ ]
128
132
 
129
133
 
130
134
  def get_mappings_df(
pyobo/cli/cli.py CHANGED
@@ -10,6 +10,7 @@ import click
10
10
 
11
11
  from .database import main as database_main
12
12
  from .lookup import lookup
13
+ from .obo_lexical_review import obo_lexical_review
13
14
  from ..constants import GLOBAL_SKIP, RAW_DIRECTORY
14
15
  from ..plugins import has_nomenclature_plugin
15
16
 
@@ -101,6 +102,7 @@ def _no_download() -> set[str]:
101
102
 
102
103
  main.add_command(lookup)
103
104
  main.add_command(database_main)
105
+ main.add_command(obo_lexical_review)
104
106
 
105
107
  if __name__ == "__main__":
106
108
  main()
pyobo/cli/lookup.py CHANGED
@@ -1,8 +1,12 @@
1
1
  """CLI for PyOBO lookups."""
2
2
 
3
+ from __future__ import annotations
4
+
5
+ import inspect
3
6
  import json
4
7
  import sys
5
- from collections.abc import Mapping
8
+ from collections.abc import Iterable, Mapping
9
+ from typing import TYPE_CHECKING
6
10
 
7
11
  import click
8
12
  from more_click import verbose_option
@@ -17,7 +21,10 @@ from .utils import (
17
21
  strict_option,
18
22
  version_option,
19
23
  )
20
- from ..constants import LookupKwargs
24
+ from ..constants import GetOntologyKwargs, LookupKwargs
25
+
26
+ if TYPE_CHECKING:
27
+ from curies import Reference
21
28
 
22
29
  __all__ = [
23
30
  "lookup",
@@ -31,9 +38,12 @@ def lookup():
31
38
 
32
39
  def lookup_annotate(f: Clickable) -> Clickable:
33
40
  """Add appropriate decorators to lookup CLI functions."""
41
+ signature = inspect.signature(f)
42
+ param = signature.parameters["kwargs"]
43
+ if param.kind is not inspect.Parameter.VAR_KEYWORD:
44
+ raise ValueError("programmer error")
34
45
  for decorator in [
35
46
  lookup.command(),
36
- prefix_argument,
37
47
  verbose_option,
38
48
  force_option,
39
49
  force_process_option,
@@ -41,6 +51,15 @@ def lookup_annotate(f: Clickable) -> Clickable:
41
51
  version_option,
42
52
  ]:
43
53
  f = decorator(f)
54
+
55
+ if param.annotation is None:
56
+ pass
57
+ if param.annotation in {"Unpack[LookupKwargs]", Unpack[LookupKwargs]}:
58
+ f = prefix_argument(f)
59
+ elif param.annotation in {"Unpack[GetOntologyKwargs]", Unpack[GetOntologyKwargs]}:
60
+ pass
61
+ else:
62
+ raise ValueError(f"unknown parameter type for {f}: {param.annotation}")
44
63
  return f
45
64
 
46
65
 
@@ -249,33 +268,35 @@ def hierarchy(
249
268
 
250
269
 
251
270
  @lookup_annotate
252
- @click.argument("identifier")
253
- def ancestors(
254
- identifier: str,
255
- **kwargs: Unpack[LookupKwargs],
256
- ) -> None:
271
+ @click.argument("curie")
272
+ def ancestors(curie: str, **kwargs: Unpack[GetOntologyKwargs]) -> None:
257
273
  """Look up ancestors."""
258
- from ..api import get_ancestors, get_name
274
+ from ..api import get_ancestors
259
275
 
260
- # note, prefix is passed via kwargs
261
- ancestors = get_ancestors(identifier=identifier, **kwargs)
262
- for ancestor in sorted(ancestors or []):
263
- click.echo(f"{ancestor.curie}\t{get_name(ancestor, version=kwargs['version'])}")
276
+ ancestors = get_ancestors(curie, **kwargs)
277
+ _list_curies(ancestors, **kwargs)
264
278
 
265
279
 
266
280
  @lookup_annotate
267
- @click.argument("identifier")
268
- def descendants(
269
- identifier: str,
270
- **kwargs: Unpack[LookupKwargs],
271
- ) -> None:
281
+ @click.argument("curie")
282
+ def descendants(curie: str, **kwargs: Unpack[GetOntologyKwargs]) -> None:
272
283
  """Look up descendants."""
273
- from ..api import get_descendants, get_name
284
+ from ..api import get_descendants
285
+
286
+ descendants = get_descendants(curie, **kwargs)
287
+ _list_curies(descendants, **kwargs)
288
+
289
+
290
+ def _list_curies(
291
+ references: Iterable[Reference] | None, **kwargs: Unpack[GetOntologyKwargs]
292
+ ) -> None:
293
+ if not references:
294
+ return
295
+
296
+ from ..api import get_name
274
297
 
275
- # note, prefix is passed via kwargs
276
- descendants = get_descendants(identifier=identifier, **kwargs)
277
- for descendant in sorted(descendants or []):
278
- click.echo(f"{descendant.curie}\t{get_name(descendant, version=kwargs['version'])}")
298
+ for reference in sorted(references or []):
299
+ click.echo(f"{reference.curie}\t{get_name(reference, version=kwargs['version'])}")
279
300
 
280
301
 
281
302
  @lookup_annotate
@@ -0,0 +1,207 @@
1
+ # /// script
2
+ # requires-python = ">=3.11"
3
+ # dependencies = [
4
+ # "click>=8.3.1",
5
+ # "obographs>=0.0.8",
6
+ # "pyperclip>=1.11.0",
7
+ # "robot-obo-tool>=0.0.1",
8
+ # "ssslm[gilda-slim]>=0.1.3",
9
+ # "tabulate>=0.9.0",
10
+ # "tqdm>=4.67.3",
11
+ # ]
12
+ # ///
13
+
14
+ """Implement lexical review for an ontology."""
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import TYPE_CHECKING
19
+
20
+ import click
21
+
22
+ if TYPE_CHECKING:
23
+ import obographs
24
+ import ssslm
25
+
26
+ INDEX_URL = "https://github.com/biopragmatics/biolexica/raw/main/lexica/obo/obo.ssslm.tsv.gz"
27
+ UPPER = {"ncit"}
28
+
29
+
30
+ @click.command()
31
+ @click.argument("prefix")
32
+ @click.option(
33
+ "--location",
34
+ help="Local path or URL to an OBO Graph JSON file or OWL file. If not given, will try and look up through the OBO PURL system",
35
+ )
36
+ @click.option(
37
+ "--uri-prefix",
38
+ help="Local path to an OBO Graph JSON file. If not given, will try and look up through the OBO PURL system",
39
+ )
40
+ @click.option("--index-url", default=INDEX_URL, show_default=True)
41
+ @click.option("--show-passed", is_flag=True)
42
+ @click.option("--skip-upper", is_flag=True, help=f"if true, skip upper level ontologies {UPPER}")
43
+ @click.option("--index-force", is_flag=True, help="if true, force re-downloading the lexical index")
44
+ def obo_lexical_review(
45
+ prefix: str,
46
+ location: str | None,
47
+ uri_prefix: str | None,
48
+ index_url: str,
49
+ show_passed: bool,
50
+ skip_upper: bool,
51
+ index_force: bool,
52
+ ) -> None:
53
+ """Make a lexical review of an ontology."""
54
+ import sys
55
+ import time
56
+
57
+ import pyperclip
58
+ import pystow
59
+ import ssslm
60
+ from tabulate import tabulate
61
+
62
+ module = pystow.module("pyobo", "obo-lexical-review")
63
+
64
+ args = " ".join(sys.argv[1:])
65
+ output = f"Analysis of {prefix} run on {time.asctime()} with the following command:\n\n```console\n$ uvx pyobo obo-lexical-review {args}\n```\n\n"
66
+
67
+ graph_document, uri_prefix = _get_graph_document(
68
+ prefix=prefix,
69
+ uri_prefix=uri_prefix,
70
+ ontology_path=location,
71
+ )
72
+
73
+ click.echo(f"Loading lexical index from {index_url} using SSSLM")
74
+ path = module.ensure(url=index_url, force=index_force)
75
+ grounder = ssslm.make_grounder(path)
76
+ click.echo("Done loading lexical index")
77
+
78
+ passed, failed = _get_calls(
79
+ graph_document=graph_document,
80
+ matcher=grounder,
81
+ uri_prefix=uri_prefix,
82
+ skip_upper=skip_upper,
83
+ )
84
+
85
+ total = len(passed) + len(failed)
86
+
87
+ if passed and show_passed:
88
+ passed_table = tabulate(passed, headers=["LUID", "Name"], tablefmt="github")
89
+ passed_msg = f"## Passed Nodes ({len(passed):,}/{total:,}; {len(passed) / total:.1%})\n\n{passed_table}\n\n"
90
+ output += passed_msg
91
+
92
+ if failed:
93
+ rows = []
94
+ for luid, name, matches in failed:
95
+ rows.append((luid, name, *_parts(matches[0])))
96
+ for match in matches[1:]:
97
+ rows.append(("", "", *_parts(match)))
98
+ failed_table = tabulate(
99
+ rows, headers=[prefix, "name", "obo-curie", "obo-name", "obo-score"], tablefmt="github"
100
+ )
101
+ failed_message = f"## Failed Nodes ({len(failed):,}/{total:,}; {len(failed) / total:.1%})\n\n{failed_table}\n\n"
102
+ output += failed_message
103
+
104
+ click.echo(output)
105
+ click.echo("Finished! automatically copied to the clipboard, e.g., for pasting into GitHub)")
106
+ pyperclip.copy(output)
107
+
108
+
109
+ def _parts(match: ssslm.Match) -> tuple[str, str, float]:
110
+ return (
111
+ f"[`{match.curie}`](https://semantic.farm/{match.curie})",
112
+ match.name or "",
113
+ round(match.score, 3),
114
+ )
115
+
116
+
117
+ def _get_graph_document(
118
+ prefix: str, uri_prefix: str | None = None, ontology_path: str | None = None
119
+ ) -> tuple[obographs.GraphDocument, str]:
120
+ from pathlib import Path
121
+
122
+ import obographs
123
+ import robot_obo_tool
124
+
125
+ if uri_prefix is None:
126
+ uri_prefix = f"http://purl.obolibrary.org/obo/{prefix}_"
127
+ click.echo(f"Inferred URI prefix from given OBO CURIE prefix: {uri_prefix}")
128
+ if ontology_path is None:
129
+ ontology_path = f"https://purl.obolibrary.org/obo/{prefix.lower()}.json"
130
+ click.echo(f"No ontology path given, guessing it's available at {ontology_path}")
131
+ if ontology_path.endswith(".json"):
132
+ click.echo(f"reading OBO Graph JSON from {ontology_path}")
133
+ graph_documents = obographs.read(ontology_path, squeeze=False, timeout=60)
134
+ else:
135
+ import tempfile
136
+
137
+ with tempfile.TemporaryDirectory() as tmpdir:
138
+ tmppath = Path(tmpdir).joinpath("temp.json")
139
+ click.echo(
140
+ "given ontology path does not end with JSON. implicitly converting to OBO Graph JSON using ROBOT"
141
+ )
142
+ robot_obo_tool.convert(
143
+ input_path=ontology_path,
144
+ output_path=tmppath,
145
+ check=False,
146
+ merge=False,
147
+ reason=False,
148
+ )
149
+ click.echo("reading converted OBO Graph JSON")
150
+ graph_documents = obographs.read(tmppath, squeeze=False)
151
+
152
+ return graph_documents, uri_prefix
153
+
154
+
155
+ def _get_calls(
156
+ *,
157
+ graph_document: obographs.GraphDocument,
158
+ matcher: ssslm.Matcher,
159
+ uri_prefix: str,
160
+ skip_upper: bool = False,
161
+ ) -> tuple[list[tuple[str, str]], list[tuple[str, str, list[ssslm.Match]]]]:
162
+ """Get matches."""
163
+ from tqdm import tqdm
164
+
165
+ passed = []
166
+ failed = []
167
+ total = 0
168
+ skipped = 0
169
+ for graph in tqdm(graph_document.graphs, unit="graph"):
170
+ for node in tqdm(sorted(graph.nodes, key=lambda n: n.id), unit="node", leave=False):
171
+ if node.id is None:
172
+ continue
173
+
174
+ total += 1
175
+ if not node.id.startswith(uri_prefix):
176
+ skipped += 1
177
+ continue
178
+
179
+ if not node.lbl:
180
+ continue
181
+
182
+ local_unique_identifier = node.id[len(uri_prefix) :]
183
+
184
+ matches: list[ssslm.Match] = []
185
+ matches.extend(matcher.get_matches(node.lbl))
186
+ if node.meta is not None and node.meta.synonyms is not None:
187
+ matches.extend(
188
+ match
189
+ for synonym in node.meta.synonyms
190
+ for match in matcher.get_matches(synonym.val)
191
+ )
192
+
193
+ # there are a lot of NCIT matches, which aren't that informative
194
+ # since OBO doesn't mind duplicating these terms
195
+ if skip_upper:
196
+ matches = [m for m in matches if m.prefix not in UPPER]
197
+
198
+ if not matches:
199
+ passed.append((local_unique_identifier, node.lbl))
200
+ else:
201
+ failed.append((local_unique_identifier, node.lbl, matches))
202
+
203
+ return passed, failed
204
+
205
+
206
+ if __name__ == "__main__":
207
+ obo_lexical_review()
pyobo/getters.py CHANGED
@@ -17,7 +17,6 @@ from pathlib import Path
17
17
  from textwrap import indent
18
18
  from typing import Any, TypeVar
19
19
 
20
- import bioontologies.robot
21
20
  import bioregistry
22
21
  import click
23
22
  import pystow.utils
@@ -80,10 +79,10 @@ REQUIRES_NO_ROBOT_CHECK = {
80
79
 
81
80
 
82
81
  def _convert_to_obo(path: Path) -> Path:
83
- import bioontologies.robot
82
+ import robot_obo_tool
84
83
 
85
84
  _converted_obo_path = path.with_suffix(".obo")
86
- bioontologies.robot.convert(path, _converted_obo_path, check=False)
85
+ robot_obo_tool.convert(path, _converted_obo_path, check=False)
87
86
  return _converted_obo_path
88
87
 
89
88
 
@@ -338,6 +337,8 @@ def iter_helper_helper(
338
337
 
339
338
  :yields: A prefix and the result of the callable ``f``
340
339
  """
340
+ from robot_obo_tool import ROBOTError
341
+
341
342
  strict = kwargs.get("strict", True)
342
343
  prefixes = list(
343
344
  _prefixes(
@@ -382,7 +383,7 @@ def iter_helper_helper(
382
383
  if "DrugBank" not in str(e):
383
384
  raise
384
385
  logger.warning("[drugbank] invalid credentials")
385
- except (subprocess.CalledProcessError, bioontologies.robot.ROBOTError):
386
+ except (subprocess.CalledProcessError, ROBOTError):
386
387
  logger.warning("[%s] ROBOT was unable to convert OWL to OBO", prefix)
387
388
  except ValueError as e:
388
389
  if _is_xml(e):
pyobo/sources/ror.py CHANGED
@@ -2,18 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import datetime
6
- import json
7
5
  import logging
8
- import zipfile
9
6
  from collections.abc import Iterable
10
- from functools import lru_cache
11
- from pathlib import Path
12
- from typing import Any, Literal, NamedTuple, TypeAlias
7
+ from typing import Any
13
8
 
14
9
  import bioregistry
15
- import zenodo_client
16
- from pydantic import BaseModel, ValidationError
10
+ import ror_downloader
11
+ from pydantic import ValidationError
12
+ from ror_downloader import OrganizationType
17
13
  from tqdm.auto import tqdm
18
14
 
19
15
  from pyobo.struct import Obo, Reference, Term
@@ -29,16 +25,12 @@ from pyobo.struct.typedef import (
29
25
  )
30
26
 
31
27
  __all__ = [
32
- "OrganizationType",
33
- "RORStatus",
34
- "get_ror_records",
35
- "get_ror_status",
28
+ "RORGetter",
36
29
  "get_ror_to_country_geonames",
37
30
  ]
38
31
 
39
32
  logger = logging.getLogger(__name__)
40
33
  PREFIX = "ror"
41
- ROR_ZENODO_RECORD_ID = "17953395"
42
34
 
43
35
  # Constants
44
36
  ORG_CLASS = Reference(prefix="OBI", identifier="0000245", name="organization")
@@ -70,7 +62,7 @@ class RORGetter(Obo):
70
62
  root_terms = [CITY_CLASS, ORG_CLASS]
71
63
 
72
64
  def __post_init__(self):
73
- self.data_version, _url, _path = get_ror_status()
65
+ self.data_version = ror_downloader.get_version_info(download=False).version
74
66
  super().__post_init__()
75
67
 
76
68
  def iter_terms(self, force: bool = False) -> Iterable[Term]:
@@ -83,18 +75,6 @@ class RORGetter(Obo):
83
75
  yield from iterate_ror_terms(force=force)
84
76
 
85
77
 
86
- OrganizationType: TypeAlias = Literal[
87
- "education",
88
- "facility",
89
- "funder",
90
- "company",
91
- "government",
92
- "healthcare",
93
- "archive",
94
- "nonprofit",
95
- "other",
96
- ]
97
-
98
78
  ROR_ORGANIZATION_TYPE_TO_OBI: dict[OrganizationType, Term] = {
99
79
  "education": Term.default(PREFIX, "education", "educational organization"),
100
80
  "facility": Term.default(PREFIX, "facility", "facility"),
@@ -115,127 +95,9 @@ for _k, v in ROR_ORGANIZATION_TYPE_TO_OBI.items():
115
95
  _MISSED_ORG_TYPES: set[str] = set()
116
96
 
117
97
 
118
- class LocationDetails(BaseModel):
119
- """The location details slot in the ROR schema."""
120
-
121
- continent_code: str
122
- continent_name: str
123
- country_code: str
124
- country_name: str
125
- country_subdivision_code: str | None = None
126
- country_subdivision_name: str | None = None
127
- lat: float
128
- lng: float
129
- name: str
130
-
131
-
132
- class Location(BaseModel):
133
- """The lcoation slot in the ROR schema."""
134
-
135
- geonames_id: int
136
- geonames_details: LocationDetails
137
-
138
-
139
- class ExternalID(BaseModel):
140
- """The external ID slot in the ROR schema."""
141
-
142
- type: str
143
- all: list[str]
144
- preferred: str | None = None
145
-
146
-
147
- class Link(BaseModel):
148
- """The link slot in the ROR schema."""
149
-
150
- type: str
151
- value: str
152
-
153
-
154
- class Name(BaseModel):
155
- """The name slot in the ROR schema."""
156
-
157
- value: str
158
- types: list[str]
159
- lang: str | None = None
160
-
161
-
162
- class Relationship(BaseModel):
163
- """The relationship slot in the ROR schema."""
164
-
165
- type: str
166
- label: str
167
- id: str
168
-
169
-
170
- class DateAnnotated(BaseModel):
171
- """The annotated date slot in the ROR schema."""
172
-
173
- date: datetime.date
174
- schema_version: str
175
-
176
-
177
- class Admin(BaseModel):
178
- """The admin slot in the ROR schema."""
179
-
180
- created: DateAnnotated
181
- last_modified: DateAnnotated
182
-
183
-
184
- Status: TypeAlias = Literal["active", "inactive", "withdrawn"]
185
-
186
-
187
- class Record(BaseModel):
188
- """A ROR record."""
189
-
190
- locations: list[Location]
191
- established: int | None = None
192
- external_ids: list[ExternalID]
193
- id: str
194
- domains: list[str]
195
- links: list[Link]
196
- names: list[Name]
197
- relationships: list[Relationship]
198
- status: Status
199
- types: list[OrganizationType]
200
- admin: Admin
201
-
202
- def get_preferred_label(self) -> str | None:
203
- """Get the preferred label."""
204
- primary_name: str | None = None
205
- for name in self.names:
206
- if "ror_display" in name.types:
207
- primary_name = name.value
208
- if primary_name is None:
209
- return None
210
- primary_name = NAME_REMAPPING.get(primary_name, primary_name)
211
- return primary_name
212
-
213
-
214
- _description_prefix = {
215
- "education": "an educational organization",
216
- "facility": "a facility",
217
- "funder": "a funder",
218
- "company": "a company",
219
- "government": "a governmental organization",
220
- "healthcare": "a healthcare organization",
221
- "archive": "an archive",
222
- "nonprofit": "a nonprofit organization",
223
- "other": "an organization",
224
- }
225
-
226
-
227
- def _get_description(record: Record) -> str | None:
228
- description = (
229
- f"{_description_prefix[record.types[0]]} in {record.locations[0].geonames_details.name}"
230
- )
231
- if record.established:
232
- description += f" established in {record.established}"
233
- return description
234
-
235
-
236
98
  def iterate_ror_terms(*, force: bool = False) -> Iterable[Term]:
237
99
  """Iterate over terms in ROR."""
238
- status, records = get_ror_records(force=force)
100
+ status, records = ror_downloader.get_organizations(force=force)
239
101
  unhandled_xref_prefixes: set[str] = set()
240
102
 
241
103
  seen_geonames_references = set()
@@ -249,7 +111,7 @@ def iterate_ror_terms(*, force: bool = False) -> Iterable[Term]:
249
111
  term = Term(
250
112
  reference=Reference(prefix=PREFIX, identifier=identifier, name=primary_name),
251
113
  type="Instance",
252
- definition=_get_description(record),
114
+ definition=record.get_description(),
253
115
  )
254
116
  for organization_type in record.types:
255
117
  if organization_type in ROR_ORGANIZATION_TYPE_TO_OBI:
@@ -339,63 +201,6 @@ def iterate_ror_terms(*, force: bool = False) -> Iterable[Term]:
339
201
  yield geonames_term
340
202
 
341
203
 
342
- class RORStatus(NamedTuple):
343
- """A version information tuple."""
344
-
345
- version: str
346
- url: str
347
- path: Path
348
-
349
-
350
- def get_ror_status(*, force: bool = False, authenticate_zenodo: bool = True) -> RORStatus:
351
- """Ensure the latest ROR record, metadata, and filepath.
352
-
353
- :param force: Should the record be downloaded again? This almost
354
- never needs to be true, since the data doesn't change for
355
- a given version
356
- :param authenticate_zenodo: Should Zenodo be authenticated?
357
- This isn't required, but can help avoid rate limits
358
- :return: A version information tuple
359
-
360
- .. note::
361
-
362
- this goes into the ``~/.data/zenodo/6347574`` folder,
363
- because 6347574 is the super-record ID, which groups all
364
- versions together. this is different from the value
365
- for :data:`ROR_ZENODO_RECORD_ID`
366
- """
367
- client = zenodo_client.Zenodo()
368
- latest_record_id = client.get_latest_record(
369
- ROR_ZENODO_RECORD_ID, authenticate=authenticate_zenodo
370
- )
371
- response = client.get_record(latest_record_id, authenticate=authenticate_zenodo)
372
- response_json = response.json()
373
- version = response_json["metadata"]["version"].lstrip("v")
374
- file_record = response_json["files"][0]
375
- name = file_record["key"]
376
- url = file_record["links"]["self"]
377
- path = client.download(latest_record_id, name=name, force=force)
378
- return RORStatus(version=version, url=url, path=path)
379
-
380
-
381
- @lru_cache
382
- def get_ror_records(
383
- *, force: bool = False, authenticate_zenodo: bool = True
384
- ) -> tuple[RORStatus, list[Record]]:
385
- """Get the latest ROR metadata and records."""
386
- status = get_ror_status(force=force, authenticate_zenodo=authenticate_zenodo)
387
- with zipfile.ZipFile(status.path) as zf:
388
- for zip_info in zf.filelist:
389
- if zip_info.filename.endswith(".json"):
390
- with zf.open(zip_info) as file:
391
- records = [
392
- Record.model_validate(record)
393
- for record in tqdm(json.load(file), unit_scale=True)
394
- ]
395
- return status, records
396
- raise FileNotFoundError
397
-
398
-
399
204
  def get_ror_to_country_geonames(**kwargs: Any) -> dict[str, str]:
400
205
  """Get a mapping of ROR ids to GeoNames IDs for countries."""
401
206
  from pyobo.sources.geonames.geonames import get_city_to_country
@@ -250,7 +250,7 @@ class Import(Box):
250
250
 
251
251
  def get_rdf_graph_oracle(boxes: list[Box], *, prefix_map: dict[str, str]) -> Graph:
252
252
  """Serialize to turtle via OFN and conversion with ROBOT."""
253
- import bioontologies.robot
253
+ import robot_obo_tool
254
254
 
255
255
  ontology = Ontology(
256
256
  iri=EXAMPLE_ONTOLOGY_IRI,
@@ -265,7 +265,7 @@ def get_rdf_graph_oracle(boxes: list[Box], *, prefix_map: dict[str, str]) -> Gra
265
265
  ofn_path.write_text(text)
266
266
  ttl_path = stub.with_suffix(".ttl")
267
267
  try:
268
- bioontologies.robot.convert(ofn_path, ttl_path)
268
+ robot_obo_tool.convert(ofn_path, ttl_path)
269
269
  except subprocess.CalledProcessError:
270
270
  raise RuntimeError(f"failed to convert axioms from:\n\n{text}") from None
271
271
  graph.parse(ttl_path)
@@ -33,7 +33,7 @@ def to_parsed_obograph_oracle(
33
33
  obo: Obo, *, converter: Converter | None = None
34
34
  ) -> og.StandardizedGraphDocument:
35
35
  """Serialize to OBO, convert to OBO Graph JSON with ROBOT, load, then parse."""
36
- import bioontologies.robot
36
+ import robot_obo_tool
37
37
 
38
38
  if converter is None:
39
39
  converter = get_converter()
@@ -43,7 +43,7 @@ def to_parsed_obograph_oracle(
43
43
  obo_path = stub.with_suffix(".obo")
44
44
  obograph_path = stub.with_suffix(".json")
45
45
  obo.write_obo(obo_path)
46
- bioontologies.robot.convert(input_path=obo_path, output_path=obograph_path)
46
+ robot_obo_tool.convert(input_path=obo_path, output_path=obograph_path)
47
47
  raw = og.read(obograph_path, squeeze=False)
48
48
  rv = raw.standardize(converter)
49
49
  for graph in rv.graphs:
pyobo/struct/struct.py CHANGED
@@ -1067,12 +1067,12 @@ class Obo:
1067
1067
 
1068
1068
  def write_owl(self, path: str | Path) -> None:
1069
1069
  """Write OWL, by first outputting OFN then converting with ROBOT."""
1070
- from bioontologies import robot
1070
+ import robot_obo_tool
1071
1071
 
1072
1072
  with tempfile.TemporaryDirectory() as directory:
1073
1073
  ofn_path = Path(directory).joinpath("tmp.ofn")
1074
1074
  self.write_ofn(ofn_path)
1075
- robot.convert(ofn_path, path)
1075
+ robot_obo_tool.convert(ofn_path, path)
1076
1076
 
1077
1077
  def write_rdf(self, path: str | Path) -> None:
1078
1078
  """Write as Turtle RDF."""
@@ -1312,19 +1312,19 @@ class Obo:
1312
1312
  tqdm.write(f"[{self._prefix_version}] writing OBO Graph to {self._obograph_path}")
1313
1313
  self.write_obograph(self._obograph_path)
1314
1314
  else:
1315
- import bioontologies.robot
1315
+ import robot_obo_tool
1316
1316
 
1317
1317
  tqdm.write(
1318
1318
  f"[{self.ontology}] converting OFN to OBO Graph at {self._obograph_path}"
1319
1319
  )
1320
- bioontologies.robot.convert(
1320
+ robot_obo_tool.convert(
1321
1321
  self._ofn_path, self._obograph_path, debug=True, merge=False, reason=False
1322
1322
  )
1323
1323
  if write_owl and (not self._owl_path.is_file() or force):
1324
1324
  tqdm.write(f"[{self._prefix_version}] writing OWL to {self._owl_path}")
1325
- import bioontologies.robot
1325
+ import robot_obo_tool
1326
1326
 
1327
- bioontologies.robot.convert(
1327
+ robot_obo_tool.convert(
1328
1328
  self._ofn_path, self._owl_path, debug=True, merge=False, reason=False
1329
1329
  )
1330
1330
  if write_ttl and (not self._ttl_path.is_file() or force):
pyobo/utils/path.py CHANGED
@@ -107,7 +107,6 @@ def ensure_json(
107
107
  version: VersionHint = None,
108
108
  name: str | None = None,
109
109
  force: bool = False,
110
- dtype=str,
111
110
  verify: bool = True,
112
111
  backend: Literal["requests", "urllib"] | None = None,
113
112
  **kwargs: Any,
pyobo/version.py CHANGED
@@ -12,7 +12,7 @@ __all__ = [
12
12
  "get_version",
13
13
  ]
14
14
 
15
- VERSION = "0.12.14"
15
+ VERSION = "0.12.16"
16
16
 
17
17
 
18
18
  def get_git_hash() -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyobo
3
- Version: 0.12.14
3
+ Version: 0.12.16
4
4
  Summary: A python package for handling and generating OBO
5
5
  Keywords: snekpack,cookiecutter,ontologies,biomedical ontologies,life sciences,natural sciences,bioinformatics,cheminformatics,Open Biomedical Ontologies,OBO
6
6
  Author: Charles Tapley Hoyt
@@ -36,11 +36,10 @@ Requires-Dist: humanize
36
36
  Requires-Dist: tabulate
37
37
  Requires-Dist: cachier
38
38
  Requires-Dist: pystow>=0.7.5
39
- Requires-Dist: bioversions>=0.8.101
39
+ Requires-Dist: bioversions>=0.8.243
40
40
  Requires-Dist: bioregistry>=0.12.30
41
- Requires-Dist: bioontologies>=0.7.2
42
41
  Requires-Dist: ssslm>=0.0.13
43
- Requires-Dist: zenodo-client>=0.4.0
42
+ Requires-Dist: zenodo-client>=0.4.1
44
43
  Requires-Dist: class-resolver>=0.6.0
45
44
  Requires-Dist: pydantic>=2.0
46
45
  Requires-Dist: curies>=0.10.17
@@ -52,10 +51,14 @@ Requires-Dist: chembl-downloader
52
51
  Requires-Dist: umls-downloader>=0.1.3
53
52
  Requires-Dist: clinicaltrials-downloader>=0.0.2
54
53
  Requires-Dist: nih-reporter-downloader>=0.0.1
54
+ Requires-Dist: ror-downloader>=0.0.4
55
55
  Requires-Dist: typing-extensions
56
56
  Requires-Dist: rdflib
57
57
  Requires-Dist: obographs>=0.0.8
58
- Requires-Dist: sssom-pydantic>=0.1.1
58
+ Requires-Dist: sssom-pydantic>=0.2.0
59
+ Requires-Dist: pytz
60
+ Requires-Dist: robot-obo-tool
61
+ Requires-Dist: pyperclip
59
62
  Requires-Dist: psycopg2-binary ; extra == 'drugcentral'
60
63
  Requires-Dist: ssslm[gilda] ; extra == 'gilda'
61
64
  Requires-Dist: ssslm[gilda-slim] ; extra == 'gilda-slim'
@@ -72,10 +75,10 @@ Maintainer: Charles Tapley Hoyt
72
75
  Maintainer-email: Charles Tapley Hoyt <cthoyt@gmail.com>
73
76
  Requires-Python: >=3.10
74
77
  Project-URL: Bug Tracker, https://github.com/biopragmatics/pyobo/issues
75
- Project-URL: Documentation, https://pyobo.readthedocs.io
76
- Project-URL: Funding, https://github.com/sponsors/cthoyt
77
78
  Project-URL: Homepage, https://github.com/biopragmatics/pyobo
78
79
  Project-URL: Repository, https://github.com/biopragmatics/pyobo.git
80
+ Project-URL: Documentation, https://pyobo.readthedocs.io
81
+ Project-URL: Funding, https://github.com/sponsors/cthoyt
79
82
  Provides-Extra: drugcentral
80
83
  Provides-Extra: gilda
81
84
  Provides-Extra: gilda-slim
@@ -14,15 +14,16 @@ pyobo/api/relations.py,sha256=Kebguj5khScU2c0Pm8dJrewu8je9DnKlNenRtmYWGKI,5875
14
14
  pyobo/api/species.py,sha256=YxzJeClfuKI-pjYLpu5797jdDeyTkXpjQDgdmc9MKwo,2376
15
15
  pyobo/api/typedefs.py,sha256=HTui0kl_VELxgji0vfBr-NBG3zrrpCU8tmuQOSWK6fw,1262
16
16
  pyobo/api/utils.py,sha256=SCMsy4QyYVhn-QK0h90Ef1XUMbyHKR1fvteRESzs-bk,5192
17
- pyobo/api/xrefs.py,sha256=szsPOMwkKmfDRb7CNgyEoe7hRQK8aX1Vm6Vcz0FYN2w,5956
17
+ pyobo/api/xrefs.py,sha256=xrs-c56GThSzIE5Rj0dmrbztxNTr22Y7NykQHeIBPk4,6070
18
18
  pyobo/cli/__init__.py,sha256=pTiLuzjjpi0P69yVjQ2kE6nBUmZuYFnqdJNDZLDkgvk,71
19
- pyobo/cli/cli.py,sha256=lyN_Hi5Hf2jyCUXjD87pG7RU3g7TnbBiOd8o-PMqIEw,2904
19
+ pyobo/cli/cli.py,sha256=nEU70l12ChHKSYWwWNzwgo-0ldOEa0FCkvH7745u5qA,2992
20
20
  pyobo/cli/database.py,sha256=iq4-eJ_8Kgim0MMnSqf1wu2hvHDZk9BWNMVuwykz2Ko,13407
21
21
  pyobo/cli/database_utils.py,sha256=jQ7qSg-oZk-Q-RC121NkUbqXeNg0K8fV96igXaK3ra4,5540
22
- pyobo/cli/lookup.py,sha256=-Tz6E3OP6HBPipVqSrMn7LyzrezbT0pngeDExF5uyn4,9467
22
+ pyobo/cli/lookup.py,sha256=1VexVdPXM2_tTL2eFyDnepcH-_fsdEDxaQId-LblAXI,10089
23
+ pyobo/cli/obo_lexical_review.py,sha256=x6AJdNPH4WLC419KNV9_dHInVfas97MWMzQhSJRqrho,6709
23
24
  pyobo/cli/utils.py,sha256=SfW9JC8olzp4AcAsDodMPdgHfteeXq0Ngd8yQ2kzOMA,1812
24
25
  pyobo/constants.py,sha256=_Y4IACPsRu5jHntoc8nEHKLGDl1Bl7YrdS0gG4qxmqw,7582
25
- pyobo/getters.py,sha256=exZNjlseRoWhshQYArWwN2KLvkk4NmCQnHM88YUYf-w,18340
26
+ pyobo/getters.py,sha256=nZjynOIXzkeytqgu1UE-WleoStQtdQ3fJeWt8_r_Qxc,18326
26
27
  pyobo/gilda_utils.py,sha256=uThAArALSNzJi-hrR9lUvtQci5VIw4Cqq2L6mtkp0Ko,1990
27
28
  pyobo/identifier_utils/__init__.py,sha256=iSHAZhOwqWZmYpOfpXNLuKouSYj7ZSViaX7BF5uT8so,793
28
29
  pyobo/identifier_utils/api.py,sha256=fik5crIOOhsHIuH-ia_oOQeKZeX5bBMj5S6occWaMeY,9365
@@ -146,7 +147,7 @@ pyobo/sources/pubchem.py,sha256=lTsc0dgrSJ5kxlcaUcUXJydi-xsMf2fI2EcWsbW8xTU,5153
146
147
  pyobo/sources/reactome.py,sha256=yzeCCy19Hxr7KFUrg1tePjN40eZLN9G4dm4q0A7izbI,5842
147
148
  pyobo/sources/rgd.py,sha256=I6HC7_GZqFPXlRIuuroOgZM7wG85jcE3Po-sry8a1XU,5194
148
149
  pyobo/sources/rhea.py,sha256=dbhyVJ1yoAQoxoWp47OKegLAmMh6qoe-7_DafJDeWrk,8164
149
- pyobo/sources/ror.py,sha256=pFKV5qzTSU2XxvZhMOB-WyMLQ6u2dsAqadACswCqyNk,13387
150
+ pyobo/sources/ror.py,sha256=osvjhinGU2XJulbne2i2zaZuD_ZuDK4mwkxL3wERNQc,8226
150
151
  pyobo/sources/selventa/__init__.py,sha256=cIIDlWL9znL2r0aZIJ2r1FN3NM9io74w-He2egzJwX8,254
151
152
  pyobo/sources/selventa/schem.py,sha256=vqN_a_NLeshj-2dRuZelfOLJQ5dyU3ZghfSRIq9EFVA,1162
152
153
  pyobo/sources/selventa/scomp.py,sha256=nshskLQeXuzf5t79STJsVKJg3R80CX3DuJBUrEhwSbc,1531
@@ -181,17 +182,17 @@ pyobo/struct/functional/__init__.py,sha256=1Tbtt_6GoarRlgffPepitJ3QtLsrMPMbhDGoQ
181
182
  pyobo/struct/functional/dsl.py,sha256=odDX33lzSkKcRjcrsteraFSc00wIOPBHJtxXe9c9RHE,101591
182
183
  pyobo/struct/functional/macros.py,sha256=yYHGmAEMpin47U7U3S7aCC3o02khWyoON4Xgpv-gHN4,13811
183
184
  pyobo/struct/functional/obo_to_functional.py,sha256=65OmttFto-tm6-7T0RQlgIKo34swVqTpz2B9ykPLNuw,13727
184
- pyobo/struct/functional/ontology.py,sha256=-a8jx-61NsI3dgCBEYNP-VeswAy8wWxLBlAEFMJucyI,9342
185
+ pyobo/struct/functional/ontology.py,sha256=1CjtKUtpjCv8MivMfENA2t0LmWx9ZDPlt1nc-0SLrZI,9332
185
186
  pyobo/struct/functional/utils.py,sha256=c-XG2SJ7ChYtz7KU3K0F-T6ocMjYevei0WxlkfkkmtE,3487
186
187
  pyobo/struct/obo/__init__.py,sha256=xJAFJ4yueWBQv3BrtTuHHJ7AzOqCgOuHgKY4RQocsW0,157
187
188
  pyobo/struct/obo/reader.py,sha256=HJ4u1c2ScA-2qxKhyKIZZo8RI2QOUSXwushK9FZGdrQ,50664
188
189
  pyobo/struct/obo/reader_utils.py,sha256=UyQVTQhXbhZ9fCizZg26o5TmeHis9-qZMKvQFwIBRgo,4423
189
190
  pyobo/struct/obograph/__init__.py,sha256=B4rNP9OCTnMXm8YpQ0rar1swn4GIw9b4gQD-IVuqHyw,452
190
- pyobo/struct/obograph/export.py,sha256=yk3MHulDM_SMohfwiFdeB62-XGJ2ZNgRUZGjGjiyonY,9945
191
+ pyobo/struct/obograph/export.py,sha256=Z2IAYK9fRvD5QtR24oxuTFN8oPFS-eKqlvv4i9-CmXk,9935
191
192
  pyobo/struct/obograph/reader.py,sha256=264yVeD8a3jGx9EaGUZVxFbSQ_pwQ_6ckVw9S8wiJfM,8525
192
193
  pyobo/struct/obograph/utils.py,sha256=je0kSkC24MU9pWRHq1_K-J5jWhjWESY6NI3TpZqvZ_Q,1516
193
194
  pyobo/struct/reference.py,sha256=qgwTa-0VIoDklQ7LjlYH-mf2WG0_uO7KlHt0PSBail4,11744
194
- pyobo/struct/struct.py,sha256=FaNyYUSFgM700xE74i8zSVQQiR4g91jbmAMOqPyJ9NQ,96669
195
+ pyobo/struct/struct.py,sha256=cxVTrRRlV8KnLaVQBK-3dLVDfpuryBKLuA59lMN2XBk,96648
195
196
  pyobo/struct/struct_utils.py,sha256=VFC82fgphTs7sBSNGn53sv2WMl7Ho9srALFq92bRFQQ,41021
196
197
  pyobo/struct/typedef.py,sha256=jvETRmNc4g09X7waSjcvEkw7g0kNteLaDOt3mjKbmDc,14871
197
198
  pyobo/struct/utils.py,sha256=zkpOE42JQIfkN0rc5qNENK03VIKmkf_57tHojMJK71Y,906
@@ -202,10 +203,10 @@ pyobo/utils/io.py,sha256=QTvGjNDkaIf78Tca23B5RW_aVweKiSsxmgwSKXcMSNo,3921
202
203
  pyobo/utils/iter.py,sha256=rYRbbaFJHxMaE0yU-rQZoCagYIrtev09uY0mxFkf5zY,1524
203
204
  pyobo/utils/misc.py,sha256=DVLxPynunSubp2xSVKFF_E57BPS3p5wU4Z4A38PI17M,8717
204
205
  pyobo/utils/ndex_utils.py,sha256=EokCWS00Wrk_4y8ldeQuUyaaC6yNzBg3DagUl-J2czY,2326
205
- pyobo/utils/path.py,sha256=snV58UHxHO6GI2QPPE46ssR4RWozaw83V59sS_I9BY8,4109
206
- pyobo/version.py,sha256=-RjPuUzkByyIQ26Vfaw4q_Cb--M-vMCnMRNP78aBv58,927
207
- pyobo-0.12.14.dist-info/licenses/LICENSE,sha256=QcgJZKGxlW5BwBNnCBL8VZLVtRvXs81Ch9lJRQSIpJg,1076
208
- pyobo-0.12.14.dist-info/WHEEL,sha256=eycQt0QpYmJMLKpE3X9iDk8R04v2ZF0x82ogq-zP6bQ,79
209
- pyobo-0.12.14.dist-info/entry_points.txt,sha256=ANgzvuwF_9_1ipCoxJtbBM6A4i2Mkt39gMPzQO6hvGs,42
210
- pyobo-0.12.14.dist-info/METADATA,sha256=3bCbmYuRjLGPhWqwRm337UrFL0Nb7iU1--Ici26iZnc,22783
211
- pyobo-0.12.14.dist-info/RECORD,,
206
+ pyobo/utils/path.py,sha256=oHfYbsfI4UaQ8_rAHqoOTCcCceXhY82_0kLUuxs5BY0,4094
207
+ pyobo/version.py,sha256=nW8tGi5FCn_intTOMB6X02kfn9x4iYN7s9z5HXWvmko,927
208
+ pyobo-0.12.16.dist-info/licenses/LICENSE,sha256=QcgJZKGxlW5BwBNnCBL8VZLVtRvXs81Ch9lJRQSIpJg,1076
209
+ pyobo-0.12.16.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
210
+ pyobo-0.12.16.dist-info/entry_points.txt,sha256=ANgzvuwF_9_1ipCoxJtbBM6A4i2Mkt39gMPzQO6hvGs,42
211
+ pyobo-0.12.16.dist-info/METADATA,sha256=y5tMlvDbm9jqjJTseIveUFKs-Sb2uBhXIVs75RFKDvg,22859
212
+ pyobo-0.12.16.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.24
2
+ Generator: uv 0.9.28
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any