Skip to content

Commit 9e804f5

Browse files
authored
Upstream ROR implementation (#471)
1 parent 381e547 commit 9e804f5

2 files changed

Lines changed: 9 additions & 203 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ dependencies = [
8686
"umls_downloader>=0.1.3",
8787
"clinicaltrials_downloader>=0.0.2",
8888
"nih-reporter-downloader>=0.0.1",
89+
"ror-downloader",
8990
"typing_extensions",
9091
"rdflib",
9192
"obographs>=0.0.8",

src/pyobo/sources/ror.py

Lines changed: 8 additions & 203 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@
22

33
from __future__ import annotations
44

5-
import datetime
6-
import json
75
import logging
8-
import zipfile
96
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
138

149
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
1713
from tqdm.auto import tqdm
1814

1915
from pyobo.struct import Obo, Reference, Term
@@ -29,16 +25,12 @@
2925
)
3026

3127
__all__ = [
32-
"OrganizationType",
33-
"RORStatus",
34-
"get_ror_records",
35-
"get_ror_status",
28+
"RORGetter",
3629
"get_ror_to_country_geonames",
3730
]
3831

3932
logger = logging.getLogger(__name__)
4033
PREFIX = "ror"
41-
ROR_ZENODO_RECORD_ID = "17953395"
4234

4335
# Constants
4436
ORG_CLASS = Reference(prefix="OBI", identifier="0000245", name="organization")
@@ -70,7 +62,7 @@ class RORGetter(Obo):
7062
root_terms = [CITY_CLASS, ORG_CLASS]
7163

7264
def __post_init__(self):
73-
self.data_version, _url, _path = get_ror_status()
65+
self.data_version, _url, _path = ror_downloader.get_version_info()
7466
super().__post_init__()
7567

7668
def iter_terms(self, force: bool = False) -> Iterable[Term]:
@@ -83,18 +75,6 @@ def iter_terms(self, force: bool = False) -> Iterable[Term]:
8375
yield from iterate_ror_terms(force=force)
8476

8577

86-
OrganizationType: TypeAlias = Literal[
87-
"education",
88-
"facility",
89-
"funder",
90-
"company",
91-
"government",
92-
"healthcare",
93-
"archive",
94-
"nonprofit",
95-
"other",
96-
]
97-
9878
ROR_ORGANIZATION_TYPE_TO_OBI: dict[OrganizationType, Term] = {
9979
"education": Term.default(PREFIX, "education", "educational organization"),
10080
"facility": Term.default(PREFIX, "facility", "facility"),
@@ -115,127 +95,9 @@ def iter_terms(self, force: bool = False) -> Iterable[Term]:
11595
_MISSED_ORG_TYPES: set[str] = set()
11696

11797

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-
23698
def iterate_ror_terms(*, force: bool = False) -> Iterable[Term]:
23799
"""Iterate over terms in ROR."""
238-
status, records = get_ror_records(force=force)
100+
status, records = ror_downloader.get_organizations(force=force)
239101
unhandled_xref_prefixes: set[str] = set()
240102

241103
seen_geonames_references = set()
@@ -249,7 +111,7 @@ def iterate_ror_terms(*, force: bool = False) -> Iterable[Term]:
249111
term = Term(
250112
reference=Reference(prefix=PREFIX, identifier=identifier, name=primary_name),
251113
type="Instance",
252-
definition=_get_description(record),
114+
definition=record.get_description(),
253115
)
254116
for organization_type in record.types:
255117
if organization_type in ROR_ORGANIZATION_TYPE_TO_OBI:
@@ -339,63 +201,6 @@ def iterate_ror_terms(*, force: bool = False) -> Iterable[Term]:
339201
yield geonames_term
340202

341203

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-
399204
def get_ror_to_country_geonames(**kwargs: Any) -> dict[str, str]:
400205
"""Get a mapping of ROR ids to GeoNames IDs for countries."""
401206
from pyobo.sources.geonames.geonames import get_city_to_country

0 commit comments

Comments
 (0)