Skip to content

Commit ac0a7da

Browse files
authored
Improve version handling (#317)
1 parent 7357785 commit ac0a7da

4 files changed

Lines changed: 91 additions & 18 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ dependencies = [
7070
"tabulate",
7171
"cachier",
7272
"pystow>=0.7.0",
73-
"bioversions>=0.8.44",
73+
"bioversions>=0.8.101",
7474
"bioregistry>=0.12.30",
7575
"bioontologies>=0.7.2",
7676
"ssslm>=0.0.13",

src/pyobo/constants.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import logging
66
import re
7+
from pathlib import Path
8+
from typing import Literal, NamedTuple, TypeAlias
79

810
import pystow
911
from typing_extensions import NotRequired, TypedDict
@@ -188,6 +190,8 @@ class IterHelperHelperDict(SlimGetOntologyKwargs):
188190
skip_set: set[str] | None
189191

190192

193+
OntologyFormat: TypeAlias = Literal["obo", "owl", "json", "rdf"]
194+
191195
#: from table 2 of the Functional OWL syntax definition
192196
#: at https://www.w3.org/TR/owl2-syntax/#IRIs
193197
DEFAULT_PREFIX_MAP = {
@@ -196,3 +200,10 @@ class IterHelperHelperDict(SlimGetOntologyKwargs):
196200
"xsd": "http://www.w3.org/2001/XMLSchema#",
197201
"owl": "http://www.w3.org/2002/07/owl#",
198202
}
203+
204+
205+
class OntologyPathPack(NamedTuple):
206+
"""A format and path tuple."""
207+
208+
format: OntologyFormat
209+
path: Path

src/pyobo/getters.py

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from collections.abc import Callable, Iterable, Mapping, Sequence
1616
from pathlib import Path
1717
from textwrap import indent
18-
from typing import Any, Literal, TypeAlias, TypeVar
18+
from typing import Any, TypeVar
1919

2020
import bioontologies.robot
2121
import bioregistry
@@ -30,19 +30,21 @@
3030
DATABASE_DIRECTORY,
3131
GetOntologyKwargs,
3232
IterHelperHelperDict,
33+
OntologyFormat,
34+
OntologyPathPack,
3335
SlimGetOntologyKwargs,
3436
)
3537
from .identifier_utils import ParseError, wrap_norm_prefix
3638
from .plugins import has_nomenclature_plugin, run_nomenclature_plugin
3739
from .struct import Obo
3840
from .struct.obo import from_obo_path, from_obonet
3941
from .utils.io import safe_open_writer
42+
from .utils.misc import VERSION_GETTERS, cleanup_version
4043
from .utils.path import ensure_path, prefix_directory_join
4144
from .version import get_git_hash, get_version
4245

4346
__all__ = [
4447
"NoBuildError",
45-
"OntologyFormat",
4648
"get_ontology",
4749
]
4850

@@ -125,10 +127,20 @@ def get_ontology(
125127
"""
126128
if force:
127129
force_process = True
130+
if has_nomenclature_plugin(prefix):
131+
obo = run_nomenclature_plugin(prefix, version=version)
132+
if cache:
133+
logger.debug("[%s] caching nomenclature plugin", prefix)
134+
obo.write_default(force=force_process)
135+
return obo
136+
128137
if prefix == "uberon":
129138
logger.info("UBERON has so much garbage in it that defaulting to non-strict parsing")
130139
strict = False
131140

141+
if version is None:
142+
version = _get_version_from_artifact(prefix)
143+
132144
if force_process:
133145
obonet_json_gz_path = None
134146
elif not cache:
@@ -157,17 +169,11 @@ def get_ontology(
157169
else:
158170
logger.debug("[%s] no obonet cache found at %s", prefix, obonet_json_gz_path)
159171

160-
if has_nomenclature_plugin(prefix):
161-
obo = run_nomenclature_plugin(prefix, version=version)
162-
if cache:
163-
logger.debug("[%s] caching nomenclature plugin", prefix)
164-
obo.write_default(force=force_process)
165-
return obo
166-
167-
ontology_format, path = _ensure_ontology_path(prefix, force=force, version=version)
168-
if path is None:
172+
path_pack = _ensure_ontology_path(prefix, force=force, version=version)
173+
if path_pack is None:
169174
raise NoBuildError(prefix)
170-
elif ontology_format == "obo":
175+
ontology_format, path = path_pack
176+
if ontology_format == "obo":
171177
pass # all gucci
172178
elif ontology_format in {"owl", "rdf"}:
173179
path = _convert_to_obo(path)
@@ -195,7 +201,6 @@ def get_ontology(
195201
return obo
196202

197203

198-
OntologyFormat: TypeAlias = Literal["obo", "owl", "json", "rdf"]
199204
# order matters in this list, since order implicitly defines priority
200205
_ONTOLOGY_GETTERS: list[tuple[OntologyFormat, Callable[[str], str | None]]] = [
201206
("obo", bioregistry.get_obo_download),
@@ -206,8 +211,8 @@ def get_ontology(
206211

207212

208213
def _ensure_ontology_path(
209-
prefix: str, force: bool, version: str | None
210-
) -> tuple[OntologyFormat, Path] | tuple[None, None]:
214+
prefix: str, *, force: bool, version: str | None
215+
) -> OntologyPathPack | None:
211216
for ontology_format, getter in _ONTOLOGY_GETTERS:
212217
url = getter(prefix)
213218
if url is None:
@@ -219,8 +224,8 @@ def _ensure_ontology_path(
219224
except pystow.utils.UnexpectedDirectoryError:
220225
continue # TODO report more info about the URL and the name it tried to make
221226
else:
222-
return ontology_format, path
223-
return None, None
227+
return OntologyPathPack(ontology_format, path)
228+
return None
224229

225230

226231
SKIP = {
@@ -519,3 +524,20 @@ def db_output_helper(
519524
click.echo()
520525

521526
return [path for _, path in rv]
527+
528+
529+
def _get_version_from_artifact(prefix: str) -> str | None:
530+
# assume that all possible files that can be downloaded
531+
# are in sync and have the same version
532+
for ontology_format, func in _ONTOLOGY_GETTERS:
533+
url = func(prefix)
534+
if url is None:
535+
continue
536+
# Try to peak into the file to get the version without fully downloading
537+
version_func = VERSION_GETTERS.get(ontology_format)
538+
if version_func is None:
539+
continue
540+
version = version_func(prefix, url)
541+
if version:
542+
return cleanup_version(version, prefix=prefix)
543+
return None

src/pyobo/utils/misc.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
"""Miscellaneous utilities."""
22

3+
from __future__ import annotations
4+
35
import logging
6+
from collections.abc import Callable
47
from datetime import datetime
58

9+
import bioversions.utils
10+
11+
from pyobo.constants import OntologyFormat
12+
613
__all__ = [
14+
"VERSION_GETTERS",
715
"cleanup_version",
816
]
917

@@ -43,6 +51,9 @@
4351

4452
def cleanup_version(data_version: str, prefix: str) -> str:
4553
"""Clean the version information."""
54+
# in case a literal string that wasn't parsed properly gets put in
55+
data_version = data_version.strip('"')
56+
4657
if data_version in VERSION_REWRITES:
4758
return VERSION_REWRITES[data_version]
4859

@@ -79,3 +90,32 @@ def cleanup_version(data_version: str, prefix: str) -> str:
7990
logger.debug("[%s] bizarre version: %s", prefix, data_version)
8091
BIZARRE_LOGGED.add((prefix, data_version))
8192
return data_version
93+
94+
95+
def _get_obo_version(prefix: str, url: str, *, max_lines: int = 200) -> str | None:
96+
rv = bioversions.utils.get_obo_version(url, max_lines=max_lines)
97+
if rv is None:
98+
return None
99+
return cleanup_version(rv, prefix)
100+
101+
102+
def _get_owl_version(prefix: str, url: str, *, max_lines: int = 200) -> str | None:
103+
rv = bioversions.utils.get_owl_xml_version(url, max_lines=max_lines)
104+
if rv is None:
105+
return None
106+
return cleanup_version(rv, prefix)
107+
108+
109+
def _get_obograph_json_version(prefix: str, url: str) -> str | None:
110+
rv = bioversions.utils.get_obograph_json_version(url)
111+
if rv is None:
112+
return None
113+
return cleanup_version(rv, prefix)
114+
115+
116+
#: A mapping from data type to gersion getter function
117+
VERSION_GETTERS: dict[OntologyFormat, Callable[[str, str], str | None]] = {
118+
"obo": _get_obo_version,
119+
"owl": _get_owl_version,
120+
"json": _get_obograph_json_version,
121+
}

0 commit comments

Comments
 (0)