Skip to content

Commit 1f66d18

Browse files
Implement Pagination for SerialTitleSearch() (close #431)
1 parent 34fbe51 commit 1f66d18

2 files changed

Lines changed: 50 additions & 11 deletions

File tree

pybliometrics/scopus/tests/test_SerialTitleSearch.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,8 @@
1111
ser2 = SerialTitleSearch({'issn': '1468-0262'}, refresh=30)
1212
# Search by publisher
1313
ser3 = SerialTitleSearch({'pub': 'Stellenbosch'}, refresh=30)
14-
# Search by subject abbreviation
15-
ser4 = SerialTitleSearch({'subj': 'COMP'}, refresh=30)
1614
# Search by subject area code
17-
ser5 = SerialTitleSearch({'subjCode': '2612'}, refresh=30)
18-
15+
ser4 = SerialTitleSearch(query={'subjCode': '2708', 'content': 'journal'}, refresh=30)
1916

2017
def test_deprecated_class():
2118
from pytest import deprecated_call
@@ -28,23 +25,22 @@ def test_deprecated_class():
2825
def test_results_title():
2926
assert len(ser1.results) == 1
3027
assert ser1.results[0]['title'] == 'SoftwareX'
28+
assert ser1.get_results_size() == 1
3129

3230

3331
def test_results_issn():
3432
assert len(ser2.results) == 1
3533
assert ser2.results[0]['title'] == 'Econometrica'
34+
assert ser1.get_results_size() == 1
3635

3736

3837
def test_results_pub():
3938
assert len(ser3.results) == 4
4039
assert ser3.results[0]['title'] == 'Akroterion'
41-
42-
43-
def test_results_subj():
44-
ser4_subj_abbs = set(i['subject_area_abbrevs'] for i in ser4.results)
45-
assert False not in ['COMP' in i for i in ser4_subj_abbs]
40+
assert ser3.get_results_size() == 4
4641

4742

4843
def test_results_subjcode():
49-
ser5_subj_codes = set(i['subject_area_codes'] for i in ser5.results)
50-
assert False not in ['2612' in i for i in ser5_subj_codes]
44+
ser4_subj_codes = set(i['subject_area_codes'] for i in ser4.results)
45+
assert False not in ['2708' in i for i in ser4_subj_codes]
46+
assert ser4.get_results_size() >= 255

pybliometrics/superclasses/base.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from math import ceil
55
from time import localtime, strftime, time
66

7+
from urllib.parse import parse_qs, urlparse
8+
79
from tqdm import tqdm
810

911
from pybliometrics.exception import ScopusQueryError
@@ -53,13 +55,18 @@ def __init__(self,
5355
ab_ref_retrieval = (api == 'AbstractRetrieval') and (params['view'] == 'REF')
5456
# Check if object retrieval
5557
obj_retrieval = (api == 'ObjectRetrieval')
58+
# Check if serial title search (special pagination)
59+
serial_search = (api == 'SerialTitleSearch')
5660

5761
if fname.exists() and not self._refresh:
5862
self._mdate = mod_ts
5963
if search_request:
6064
self._json = [loads(line) for line in
6165
fname.read_text().split("\n") if line]
6266
self._n = len(self._json)
67+
elif serial_search:
68+
self._json = loads(fname.read_text())
69+
self._n = len(self._json['serial-metadata-response'].get('entry', []))
6370
elif obj_retrieval:
6471
self._object = fname.read_bytes()
6572
else:
@@ -73,6 +80,11 @@ def __init__(self,
7380
data = _get_all_refs(url, params, verbose, resp, **kwds)
7481
self._json = data
7582
data = [data]
83+
elif serial_search:
84+
entries = _get_all_serial_results(url, params, verbose, resp, **kwds)
85+
self._json = {'serial-metadata-response': {'entry': entries}}
86+
self._n = len(entries)
87+
data = [self._json]
7688
elif search_request:
7789
# Get number of results
7890
res = resp.json()
@@ -210,3 +222,34 @@ def _get_all_refs(url: str, params: dict, verbose: bool, resp: dict, **kwds) ->
210222
print(f'Total data: {len(parse_content.chained_get(data, ["abstracts-retrieval-response", "references", "reference"]))}')
211223

212224
return data
225+
226+
227+
def _get_all_serial_results(url: str, params: dict, verbose: bool, resp, **kwds) -> list:
228+
"""Get all results for `SerialTitleSearch` with pagination."""
229+
res = resp.json()
230+
data = res.get('serial-metadata-response', {}).get('entry', [])
231+
232+
# Check for 'next' link to determine if pagination is needed
233+
links = res.get('serial-metadata-response', {}).get('link', [])
234+
next_link = next((l for l in links if l.get('@ref') == 'next'), None)
235+
last_link = next((l for l in links if l.get('@ref') == 'last'), None)
236+
237+
if not next_link or not last_link:
238+
return data
239+
240+
# Calculate total from last link (start + count)
241+
last_url = last_link.get('@href', '')
242+
parsed = parse_qs(urlparse(last_url).query)
243+
last_start = int(parsed.get('start', [0])[0])
244+
count = int(parsed.get('count', [params['count']])[0])
245+
n_total = last_start + count
246+
n_chunks = ceil(n_total / count)
247+
248+
for i in tqdm(range(1, n_chunks), disable=not verbose, initial=1, total=n_chunks):
249+
params['start'] = i * count
250+
resp = get_content(url, 'SerialTitleSearch', params, **kwds)
251+
res = resp.json()
252+
entries = res.get('serial-metadata-response', {}).get('entry', [])
253+
data.extend(entries)
254+
255+
return data

0 commit comments

Comments
 (0)