Skip to content

Commit b9914a8

Browse files
authored
Merge pull request #14 from Princeton-CDH/release/0.4
Release/0.4
2 parents 1d6be50 + d295001 commit b9914a8

File tree

7 files changed

+23242
-703
lines changed

7 files changed

+23242
-703
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
CHANGELOG
44
=========
55

6+
0.4
7+
---
8+
9+
* Updates for new VIAF /search API endpoint response
10+
* bugfix: VIAF entity request needs Accept header
11+
612
0.3
713
---
814

viapy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.3.0"
1+
__version__ = "0.4.0"

viapy/api.py

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import re
23
import time
34

45
from attrdict import AttrMap
@@ -66,14 +67,13 @@ def search(self, query):
6667
search_url = "%s/search" % self.api_base
6768
params = {
6869
"query": query,
69-
"httpAccept": "application/json",
70-
"maximumRecords": 50, # TODO: configurable ?
70+
"maximumRecords": 10, # TODO: configurable ?
7171
# sort by number of holdings (default sort on web search)
7272
# - so better known names show up first
73-
"sortKeys": "holdingscount",
73+
"sortKey": "holdingscount",
7474
}
7575

76-
response = requests.get(search_url, params=params)
76+
response = requests.get(search_url, params=params, headers={"Accept": "application/json"})
7777
logger.debug(
7878
"search '%s': %s %s, %0.2f",
7979
params["query"],
@@ -130,7 +130,10 @@ def rdf(self):
130130
"""VIAF data for this entity as :class:`rdflib.Graph`"""
131131
start = time.time()
132132
graph = rdflib.Graph()
133-
graph.parse(self.uri)
133+
# 2025 update: Accept header now required, so use requests.get to retrieve RDF
134+
response = requests.get(self.uri, headers={"Accept": "application/rdf+xml"})
135+
response.raise_for_status() # raise HTTPError on non-success status
136+
graph.parse(data=response.text, format="xml")
134137
logger.debug("Loaded VIAF RDF %s: %0.2f sec", self.uri, time.time() - start)
135138
return graph
136139

@@ -183,37 +186,59 @@ def __init__(self, data):
183186
@cached_property
184187
def total_results(self):
185188
"""number of records matching the query"""
186-
return int(self._data.get("numberOfRecords", 0))
189+
return int(self._data.get("numberOfRecords", {}).get("content", 0))
187190

188191
@cached_property
189192
def records(self):
190-
"""list of results as :class:`SRUItem`."""
191-
return [SRUItem(d["record"]) for d in self._data.get("records", [])]
193+
"""List of results as :class:`SRUItem`."""
194+
record_or_records = self._data.get("records", {}).get("record")
195+
if isinstance(record_or_records, dict):
196+
return [SRUItem(self.normalize_record(record_or_records))]
197+
elif isinstance(record_or_records, list):
198+
return [SRUItem(self.normalize_record(d)) for d in record_or_records]
199+
return []
192200

201+
def normalize_record(self, data):
202+
"""Added in May 2025 to match updates to the /search API records, where
203+
the JSON response now uses namespaced keys that increase per result:
204+
ns2, ns3, ns4, and so on, applying to most subkeys (ns2:VIAFCluster,
205+
ns2:Document, etc). This method strips all nsX: prefixes recursively"""
206+
if isinstance(data, dict):
207+
return {
208+
re.sub(r"^ns\d+:", "", key): self.normalize_record(value)
209+
for key, value in data.items()
210+
}
211+
elif isinstance(data, list):
212+
return [self.normalize_record(item) for item in data]
213+
else:
214+
return data
193215

194216
class SRUItem(AttrMap):
195217
"""Single item returned by a SRU search, for use with
196-
:meth:`ViafAPI.search` and :class:`SRUResult`."""
218+
:meth:`ViafAPI.search` and :class:`SRUResult`.
219+
220+
The `VIAFCluster` attribute was added to each property lookup in 2025 to
221+
match updates to the /search API's JSON response."""
197222

198223
@property
199224
def uri(self):
200225
"""VIAF URI for this result"""
201-
return self.recordData.Document["@about"]
226+
return self.recordData.VIAFCluster.Document["about"]
202227

203228
@property
204229
def viaf_id(self):
205230
"""VIAF numeric identifier"""
206-
return self.recordData.viafID
231+
return self.recordData.VIAFCluster.viafID
207232

208233
@property
209234
def nametype(self):
210235
"""type of name (personal, corporate, title, etc)"""
211-
return self.recordData.nameType
236+
return self.recordData.VIAFCluster.nameType
212237

213238
@property
214239
def label(self):
215240
"""first main heading for this item"""
216241
try:
217-
return self.recordData.mainHeadings.data[0].text
218-
except KeyError:
219-
return self.recordData.mainHeadings.data.text
242+
return self.recordData.VIAFCluster.mainHeadings.data[0].text
243+
except (KeyError, IndexError):
244+
return self.recordData.VIAFCluster.mainHeadings.data.text

0 commit comments

Comments
 (0)