Skip to content

Commit

Permalink
Update factory interfaces for directly loading from path or URL (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
cthoyt authored Jan 14, 2023
1 parent bddfcb2 commit 867702c
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 20 deletions.
66 changes: 46 additions & 20 deletions src/curies/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import csv
import itertools as itt
import json
import warnings
from collections import defaultdict
from dataclasses import dataclass, field
from pathlib import Path
Expand All @@ -20,7 +22,9 @@
Sequence,
Set,
Tuple,
TypeVar,
Union,
cast,
)

import requests
Expand All @@ -37,6 +41,9 @@
"chain",
]

X = TypeVar("X")
LocationOr = Union[str, Path, X]


@dataclass
class Record:
Expand Down Expand Up @@ -133,6 +140,21 @@ def _get_reverse_prefix_map(records: List[Record]) -> Mapping[str, str]:
return rv


def _prepare(data: LocationOr[X]) -> X:
if isinstance(data, Path):
with data.open() as file:
return cast(X, json.load(file))
elif isinstance(data, str):
if any(data.startswith(p) for p in ("https://", "http://", "ftp://")):
res = requests.get(data)
res.raise_for_status()
return cast(X, res.json())
with open(data) as file:
return cast(X, json.load(file))
else:
return data


class Converter:
"""A cached prefix map data structure.
Expand Down Expand Up @@ -214,13 +236,12 @@ def from_extended_prefix_map_url(cls, url: str, **kwargs: Any) -> "Converter":
>>> url = "https://github.com/biopragmatics/bioregistry/raw/main/exports/contexts/bioregistry.epm.json"
>>> converter = Converter.from_extended_prefix_map_url(url)
"""
res = requests.get(url)
res.raise_for_status()
return cls.from_extended_prefix_map(res.json(), **kwargs)
warnings.warn("directly use Converter.from_extended_prefix_map", DeprecationWarning)
return cls.from_extended_prefix_map(url, **kwargs)

@classmethod
def from_extended_prefix_map(
cls, records: Iterable[Union[Record, Dict[str, Any]]], **kwargs: Any
cls, records: LocationOr[Iterable[Union[Record, Dict[str, Any]]]], **kwargs: Any
) -> "Converter":
"""Get a converter from a list of dictionaries by creating records out of them.
Expand Down Expand Up @@ -268,13 +289,16 @@ def from_extended_prefix_map(
"""
return cls(
records=[
record if isinstance(record, Record) else Record(**record) for record in records
record if isinstance(record, Record) else Record(**record)
for record in _prepare(records)
],
**kwargs,
)

@classmethod
def from_priority_prefix_map(cls, data: Mapping[str, List[str]], **kwargs: Any) -> "Converter":
def from_priority_prefix_map(
cls, data: LocationOr[Mapping[str, List[str]]], **kwargs: Any
) -> "Converter":
"""Get a converter from a priority prefix map.
:param data:
Expand Down Expand Up @@ -305,13 +329,15 @@ def from_priority_prefix_map(cls, data: Mapping[str, List[str]], **kwargs: Any)
Record(
prefix=prefix, uri_prefix=uri_prefixes[0], uri_prefix_synonyms=uri_prefixes[1:]
)
for prefix, uri_prefixes in data.items()
for prefix, uri_prefixes in _prepare(data).items()
],
**kwargs,
)

@classmethod
def from_prefix_map(cls, prefix_map: Mapping[str, str], **kwargs: Any) -> "Converter":
def from_prefix_map(
cls, prefix_map: LocationOr[Mapping[str, str]], **kwargs: Any
) -> "Converter":
"""Get a converter from a simple prefix map.
:param prefix_map:
Expand Down Expand Up @@ -342,13 +368,15 @@ def from_prefix_map(cls, prefix_map: Mapping[str, str], **kwargs: Any) -> "Conve
return cls(
[
Record(prefix=prefix, uri_prefix=uri_prefix)
for prefix, uri_prefix in prefix_map.items()
for prefix, uri_prefix in _prepare(prefix_map).items()
],
**kwargs,
)

@classmethod
def from_reverse_prefix_map(cls, reverse_prefix_map: Mapping[str, str]) -> "Converter":
def from_reverse_prefix_map(
cls, reverse_prefix_map: LocationOr[Mapping[str, str]]
) -> "Converter":
"""Get a converter from a reverse prefix map.
:param reverse_prefix_map:
Expand Down Expand Up @@ -379,7 +407,7 @@ def from_reverse_prefix_map(cls, reverse_prefix_map: Mapping[str, str]) -> "Conv
'CHEBI:138488'
"""
dd = defaultdict(list)
for uri_prefix, prefix in reverse_prefix_map.items():
for uri_prefix, prefix in _prepare(reverse_prefix_map).items():
dd[prefix].append(uri_prefix)
records = []
for prefix, uri_prefixes in dd.items():
Expand All @@ -405,20 +433,19 @@ def from_reverse_prefix_map_url(cls, url: str) -> "Converter":
>>> "chebi" in Converter.prefix_map
True
"""
res = requests.get(url)
res.raise_for_status()
return cls.from_reverse_prefix_map(res.json())
warnings.warn("directly use Converter.from_reverse_prefix_map", DeprecationWarning)
return cls.from_reverse_prefix_map(url)

@classmethod
def from_jsonld(cls, data: Dict[str, Any]) -> "Converter":
def from_jsonld(cls, data: LocationOr[Dict[str, Any]]) -> "Converter":
"""Get a converter from a JSON-LD object, which contains a prefix map in its ``@context`` key.
:param data:
A JSON-LD object
:return:
A converter
"""
return cls.from_prefix_map(data["@context"])
return cls.from_prefix_map(_prepare(data)["@context"])

@classmethod
def from_jsonld_url(cls, url: str) -> "Converter":
Expand All @@ -435,9 +462,8 @@ def from_jsonld_url(cls, url: str) -> "Converter":
>>> "rdf" in converter.prefix_map
True
"""
res = requests.get(url)
res.raise_for_status()
return cls.from_jsonld(res.json())
warnings.warn("directly use Converter.from_jsonld", DeprecationWarning)
return cls.from_jsonld(url)

@classmethod
def from_jsonld_github(
Expand Down Expand Up @@ -466,7 +492,7 @@ def from_jsonld_github(
raise ValueError("final path argument should end with .jsonld")
rest = "/".join(path)
url = f"https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{rest}"
return cls.from_jsonld_url(url)
return cls.from_jsonld(url)

def get_prefixes(self) -> Set[str]:
"""Get the set of prefixes covered by this converter."""
Expand Down
18 changes: 18 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""Trivial version test."""

import json
import tempfile
import unittest
from pathlib import Path
from tempfile import TemporaryDirectory
Expand Down Expand Up @@ -150,6 +151,23 @@ def test_bioregistry_editable(self):
converter = Converter.from_extended_prefix_map(records)
self.assert_bioregistry_converter(converter)

def test_load_path(self):
"""Test loading from paths."""
with tempfile.TemporaryDirectory() as directory:
path = Path(directory).joinpath("pm.json")
with self.assertRaises(FileNotFoundError):
Converter.from_prefix_map(path)
with self.assertRaises(FileNotFoundError):
Converter.from_prefix_map(str(path))

path.write_text(json.dumps(self.converter.prefix_map))

c1 = Converter.from_prefix_map(path)
self.assertEqual(self.converter.prefix_map, c1.prefix_map)

c2 = Converter.from_prefix_map(str(path))
self.assertEqual(self.converter.prefix_map, c2.prefix_map)

def test_reverse_constructor(self):
"""Test constructing from a reverse prefix map."""
converter = Converter.from_reverse_prefix_map(
Expand Down

0 comments on commit 867702c

Please sign in to comment.