From b9ec2d415e0b2e45ee7e626f38a4fa5179c0543d Mon Sep 17 00:00:00 2001 From: wohnlice Date: Thu, 22 May 2025 13:42:49 -0400 Subject: [PATCH 1/5] add tests for orcid --- CHANGES.md | 4 ++++ pub/tools/__init__.py | 2 +- pub/tools/entrez.py | 4 +++- pub/tools/orcid.py | 17 +++++++++++++++++ pub/tools/schema.py | 1 + requirements.txt | 4 ---- tests/test_entrez.py | 12 +++++++++++- 7 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 pub/tools/orcid.py delete mode 100644 requirements.txt diff --git a/CHANGES.md b/CHANGES.md index 8fbe9d9..d949919 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changelog +## [5.3a0] - 21 May 2025 +- add API to get name from ORCID +- add identifiers to Person + ## [5.2] - 16 May 2025 - add search by author_ids diff --git a/pub/tools/__init__.py b/pub/tools/__init__.py index 766e493..7b3868f 100644 --- a/pub/tools/__init__.py +++ b/pub/tools/__init__.py @@ -2,4 +2,4 @@ __all__ = [journals] -__version__ = "5.2" +__version__ = "5.3a0" diff --git a/pub/tools/entrez.py b/pub/tools/entrez.py index 58d6529..e79a36f 100644 --- a/pub/tools/entrez.py +++ b/pub/tools/entrez.py @@ -13,6 +13,7 @@ from .schema import Abstract from .schema import BookRecord from .schema import ChapterRecord +from .schema import EntrezRecord from .schema import Grant from .schema import JournalRecord from .schema import Person @@ -180,6 +181,7 @@ def _parse_author_name(author: dict, investigator: bool = False) -> Person: collective_name=author.get("CollectiveName", ""), suffix=author.get("Suffix", ""), investigator=investigator, + identifiers={source.attributes["Source"]: str(source) for source in author.get("Identifier", {})}, affiliations=author.get("affiliations", []), ) @@ -583,7 +585,7 @@ def find_publications( affl=None, doi="", inclusive=False, -): +) -> list[EntrezRecord]: """ You can use the resulting WebEnv and QueryKey values to call get_searched_publications https://www.ncbi.nlm.nih.gov/books/NBK3827/#_pubmedhelp_Search_Field_Descriptions_and_ diff --git a/pub/tools/orcid.py b/pub/tools/orcid.py new file mode 100644 index 0000000..fb41480 --- /dev/null +++ b/pub/tools/orcid.py @@ -0,0 +1,17 @@ +import requests + +PUBLIC_API = "https://pub.orcid.org/v3.0/" + + +def get_author(orcid: str, full: bool = False) -> dict: + response = requests.get(f"{PUBLIC_API}{orcid}", headers={"Accept": "application/json"}, timeout="2.0") + if response.status_code != 200: + raise requests.exceptions.HTTPError(f"REST API returned: {response.status_code}") + + data = response.json() + if full: + return data + given_name = data["person"].get("name", {}).get("given-names", {}).get("value") + family_name = data["person"].get("name", {}).get("family-name", {}).get("value") + + return {"given_name": given_name, "family_name": family_name} diff --git a/pub/tools/schema.py b/pub/tools/schema.py index 04c6417..e227061 100644 --- a/pub/tools/schema.py +++ b/pub/tools/schema.py @@ -16,6 +16,7 @@ class Person: suffix: str = "" investigator: bool = False affiliations: list[str] = dataclasses.field(default_factory=list) + identifiers: dict[str, str] = dataclasses.field(default_factory=dict) def asdict(self): base = dataclasses.asdict(self) diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 1f7fe59..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -biopython -unidecode -lxml -requests \ No newline at end of file diff --git a/tests/test_entrez.py b/tests/test_entrez.py index 28097c5..fffd723 100644 --- a/tests/test_entrez.py +++ b/tests/test_entrez.py @@ -4,6 +4,7 @@ from pub.tools import citations from pub.tools import entrez +from pub.tools import orcid from pub.tools.schema import Abstract from pub.tools.schema import Grant from pub.tools.schema import JournalRecord @@ -447,6 +448,15 @@ def test_find_and_fetch(self): record = entrez.get_searched_publications(record["WebEnv"], record["QueryKey"]) self.check_pub_data(record[0]) - def test_orcid(self): + def test_orcid_search(self): record = entrez.find_publications(author_ids=["0000-0002-8953-3940"]) assert int(record["Count"]) > 0 + + def test_pubmed_orcid_author(self): + record = entrez.get_publication(pmid="32570285") + assert record.authors[0].identifiers == {"ORCID": "0000-0002-1771-9287"} + + def test_get_orcid(self): + record = orcid.get_author(orcid="0000-0002-1771-9287") + assert record["given_name"] == "Rachel" + assert record["family_name"] == "Altshuler" From bd2440f2b18b608ab4c77f927694c42e17286b81 Mon Sep 17 00:00:00 2001 From: wohnlice Date: Thu, 22 May 2025 13:50:40 -0400 Subject: [PATCH 2/5] wrong data type --- pub/tools/orcid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pub/tools/orcid.py b/pub/tools/orcid.py index fb41480..6de93cd 100644 --- a/pub/tools/orcid.py +++ b/pub/tools/orcid.py @@ -4,7 +4,7 @@ def get_author(orcid: str, full: bool = False) -> dict: - response = requests.get(f"{PUBLIC_API}{orcid}", headers={"Accept": "application/json"}, timeout="2.0") + response = requests.get(f"{PUBLIC_API}{orcid}", headers={"Accept": "application/json"}, timeout=2.0) if response.status_code != 200: raise requests.exceptions.HTTPError(f"REST API returned: {response.status_code}") From 4744f01b6f0cff1afe4cea5a2a2c97f4703e8ab0 Mon Sep 17 00:00:00 2001 From: wohnlice Date: Thu, 22 May 2025 16:54:46 -0400 Subject: [PATCH 3/5] do just orcid for now --- CHANGES.md | 2 +- pub/tools/entrez.py | 4 +++- pub/tools/schema.py | 2 +- tests/test_entrez.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d949919..c0d3edf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ ## [5.3a0] - 21 May 2025 - add API to get name from ORCID -- add identifiers to Person +- add ORCID Person ## [5.2] - 16 May 2025 - add search by author_ids diff --git a/pub/tools/entrez.py b/pub/tools/entrez.py index e79a36f..e0c3dac 100644 --- a/pub/tools/entrez.py +++ b/pub/tools/entrez.py @@ -174,6 +174,8 @@ def _parse_author_name(author: dict, investigator: bool = False) -> Person: # strip excess spaces like in # https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=22606070&retmode=xml fname = " ".join([part for part in fname.split(" ") if part]) + orcid = [i for i in author.get("Identifier", []) if i.attributes.get("Source", "") == "ORCID"] + orcid = str(orcid[0]) if orcid else "" return Person( last_name=author.get("LastName", ""), first_name=fname, @@ -181,7 +183,7 @@ def _parse_author_name(author: dict, investigator: bool = False) -> Person: collective_name=author.get("CollectiveName", ""), suffix=author.get("Suffix", ""), investigator=investigator, - identifiers={source.attributes["Source"]: str(source) for source in author.get("Identifier", {})}, + orcid=orcid, affiliations=author.get("affiliations", []), ) diff --git a/pub/tools/schema.py b/pub/tools/schema.py index e227061..2285c21 100644 --- a/pub/tools/schema.py +++ b/pub/tools/schema.py @@ -16,7 +16,7 @@ class Person: suffix: str = "" investigator: bool = False affiliations: list[str] = dataclasses.field(default_factory=list) - identifiers: dict[str, str] = dataclasses.field(default_factory=dict) + orcid: str = "" def asdict(self): base = dataclasses.asdict(self) diff --git a/tests/test_entrez.py b/tests/test_entrez.py index fffd723..ce762ad 100644 --- a/tests/test_entrez.py +++ b/tests/test_entrez.py @@ -454,7 +454,7 @@ def test_orcid_search(self): def test_pubmed_orcid_author(self): record = entrez.get_publication(pmid="32570285") - assert record.authors[0].identifiers == {"ORCID": "0000-0002-1771-9287"} + assert record.authors[0].orcid == "0000-0002-1771-9287" def test_get_orcid(self): record = orcid.get_author(orcid="0000-0002-1771-9287") From 1a6d15f228b778a9b2a6036ab6e2e8a8f57c7103 Mon Sep 17 00:00:00 2001 From: wohnlice Date: Fri, 23 May 2025 10:30:33 -0400 Subject: [PATCH 4/5] let's do full identifiers with orcid shortcut --- pub/tools/entrez.py | 5 ++--- pub/tools/schema.py | 8 +++++++- tests/test_entrez.py | 5 +++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pub/tools/entrez.py b/pub/tools/entrez.py index e0c3dac..e8ceb6b 100644 --- a/pub/tools/entrez.py +++ b/pub/tools/entrez.py @@ -174,8 +174,7 @@ def _parse_author_name(author: dict, investigator: bool = False) -> Person: # strip excess spaces like in # https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=22606070&retmode=xml fname = " ".join([part for part in fname.split(" ") if part]) - orcid = [i for i in author.get("Identifier", []) if i.attributes.get("Source", "") == "ORCID"] - orcid = str(orcid[0]) if orcid else "" + identifiers = {source.attributes.get("Source", ""): str(source) for source in author.get("Identifier", [])} return Person( last_name=author.get("LastName", ""), first_name=fname, @@ -183,7 +182,7 @@ def _parse_author_name(author: dict, investigator: bool = False) -> Person: collective_name=author.get("CollectiveName", ""), suffix=author.get("Suffix", ""), investigator=investigator, - orcid=orcid, + identifiers=identifiers, affiliations=author.get("affiliations", []), ) diff --git a/pub/tools/schema.py b/pub/tools/schema.py index 2285c21..0af1061 100644 --- a/pub/tools/schema.py +++ b/pub/tools/schema.py @@ -16,7 +16,7 @@ class Person: suffix: str = "" investigator: bool = False affiliations: list[str] = dataclasses.field(default_factory=list) - orcid: str = "" + identifiers: dict[str, str] = dataclasses.field(default_factory=dict) def asdict(self): base = dataclasses.asdict(self) @@ -25,6 +25,7 @@ def asdict(self): "fname": self.fname, "cname": self.cname, "iname": self.iname, + "orcid": self.orcid, }) return base @@ -48,6 +49,11 @@ def cname(self): """backwards compatibility""" return self.collective_name + @property + def orcid(self): + """Derivative from identifiers""" + return self.identifiers.get("ORCID", "") + @dataclasses.dataclass class Abstract: diff --git a/tests/test_entrez.py b/tests/test_entrez.py index ce762ad..9dddbf7 100644 --- a/tests/test_entrez.py +++ b/tests/test_entrez.py @@ -455,6 +455,11 @@ def test_orcid_search(self): def test_pubmed_orcid_author(self): record = entrez.get_publication(pmid="32570285") assert record.authors[0].orcid == "0000-0002-1771-9287" + assert record.authors[0].asdict()["orcid"] == "0000-0002-1771-9287" + + def test_full_identifiers(self): + record = entrez.get_publication(pmid="32570285") + assert record.authors[0].identifiers == {"ORCID": "0000-0002-1771-9287"} def test_get_orcid(self): record = orcid.get_author(orcid="0000-0002-1771-9287") From e9b8fd8a30bb493694f110176a5cad9a2f9271f0 Mon Sep 17 00:00:00 2001 From: wohnlice Date: Thu, 26 Jun 2025 15:09:58 -0400 Subject: [PATCH 5/5] update jlist --- CHANGES.md | 3 +++ pub/tools/__init__.py | 2 +- pub/tools/journals.py | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c0d3edf..ef4a9bf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ # Changelog +## [5.3.0] - 26 June 2025 +- update format expected for jlist.csv dates + ## [5.3a0] - 21 May 2025 - add API to get name from ORCID - add ORCID Person diff --git a/pub/tools/__init__.py b/pub/tools/__init__.py index 7b3868f..23bfda7 100644 --- a/pub/tools/__init__.py +++ b/pub/tools/__init__.py @@ -2,4 +2,4 @@ __all__ = [journals] -__version__ = "5.3a0" +__version__ = "5.3.0" diff --git a/pub/tools/journals.py b/pub/tools/journals.py index 81ef1b9..6c52dc8 100644 --- a/pub/tools/journals.py +++ b/pub/tools/journals.py @@ -79,8 +79,8 @@ def _parse_journals(text): deposit, url, ) = row - latest = latest.split(";")[-1] - earliest = earliest.split(";")[-1] + latest = latest.split(" ")[-1] + earliest = earliest.split(" ")[-1] _atoj[abbr.lower()] = title _jtoa[title.lower()] = abbr dates[abbr.lower()] = (earliest, latest)