Skip to content

Commit 5e8b22d

Browse files
authored
fix: loop over paginated proposals (#459)
1 parent c4b4e25 commit 5e8b22d

File tree

3 files changed

+79
-11
lines changed

3 files changed

+79
-11
lines changed

importTools/update_locations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def __init__(self, name):
161161

162162
scilog = ClientSettingsFromEnv("SCILOG")
163163

164-
cat = SciCat(**scicat.__dict__)
164+
cat = SciCat(**scicat.__dict__, return_options={"lazy": True})
165165
props = cat.proposals
166166

167167
log = SciLog(**scilog.__dict__)

sdk/python/scilog/scicat.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from json import dumps
2+
from urllib.parse import quote
3+
14
from .authmixin import HEADER_JSON, AuthError
25
from .httpclient import HttpClient
36

@@ -20,13 +23,34 @@ def authenticate(self, username, password):
2023

2124

2225
class SciCat:
23-
def __init__(self, *args, **kwargs):
26+
max_iterations = 1000
27+
28+
def __init__(self, *args, return_options=None, **kwargs):
29+
self.return_options = return_options or {}
2430
self.http_client = SciCatRestAPI(*args, **kwargs)
2531

32+
def _proposals_batch(self):
33+
url = self.http_client.address + "/proposals"
34+
limit = 500
35+
filter = {"limits": {"skip": 0, "limit": limit}}
36+
iteration = 0
37+
while True:
38+
iteration += 1
39+
if iteration > self.max_iterations:
40+
raise RuntimeError("Exceeded maximum iterations in proposals_batch")
41+
proposals = self.http_client.get_request(
42+
f"{url}?filter={quote(dumps(filter))}", headers=HEADER_JSON
43+
)
44+
if not proposals or len(proposals) == 0:
45+
break
46+
yield proposals
47+
filter["limits"]["skip"] += limit
48+
2649
@property
2750
def proposals(self):
28-
url = self.http_client.address + "/proposals"
29-
return self.http_client.get_request(url, headers=HEADER_JSON)
51+
lazy = self.return_options.get("lazy", False)
52+
generator = (proposal for batch in self._proposals_batch() for proposal in batch)
53+
return generator if lazy else list(generator)
3054

3155

3256
class SciCatAuthError(AuthError):

sdk/python/tests/test_scicat.py

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
from unittest.mock import ANY, Mock, patch
1+
from json import dumps
2+
from unittest.mock import ANY, Mock, call, patch
3+
from urllib.parse import quote
24

35
import pytest
46

57
from scilog import SciCat
68

9+
ADDRESS = "http://scicat"
10+
711

812
@patch("requests.post")
913
@patch("requests.get")
@@ -16,33 +20,73 @@
1620
],
1721
)
1822
def test_get_proposals(mock_get, mock_post, token_prefix):
19-
address = "http://scicat"
2023
options = {
2124
"username": f"username{token_prefix}",
2225
"password": "password",
23-
"login_path": f"{address}/login",
26+
"login_path": f"{ADDRESS}/login",
2427
"token_prefix": token_prefix,
2528
}
2629
headers = {"Content-type": "application/json", "Accept": "application/json"}
2730
token = "token123"
2831

29-
scicat = SciCat(address, options=options)
32+
scicat = SciCat(ADDRESS, options=options)
33+
scicat.http_client.config = {}
3034
mock_response = Mock()
3135
mock_response.json.return_value = {"id": token}
3236
mock_post.return_value = mock_response
33-
scicat.http_client.config = {}
34-
scicat.proposals
37+
list(scicat.proposals)
3538
mock_post.assert_called_with(
3639
options["login_path"],
3740
json={"username": options["username"], "password": options["password"]},
3841
headers=headers,
3942
timeout=ANY,
4043
verify=True,
4144
)
45+
filter = {"limits": {"skip": 0, "limit": 500}}
4246
mock_get.assert_called_with(
43-
f"{address}/proposals",
47+
f"{ADDRESS}/proposals?filter={quote(dumps(filter))}",
4448
params=None,
4549
headers={**headers, "Authorization": f"{token_prefix or ''}{token}"},
4650
timeout=ANY,
4751
verify=True,
4852
)
53+
54+
55+
@patch("scilog.scicat.SciCatRestAPI.get_request")
56+
def test__proposals_batch(mock_get):
57+
scicat = SciCat(ADDRESS)
58+
scicat.http_client.config = {}
59+
60+
mock_get.side_effect = [[1, 2], [3, 4], []]
61+
filters = [{"limits": {"skip": 0, "limit": 500}}, {"limits": {"skip": 500, "limit": 500}}]
62+
for _ in scicat._proposals_batch():
63+
continue
64+
65+
assert mock_get.call_count == 3
66+
67+
expected_calls = [
68+
call(f"{ADDRESS}/proposals?filter={quote(dumps(filters[0]))}", headers=ANY),
69+
call(f"{ADDRESS}/proposals?filter={quote(dumps(filters[1]))}", headers=ANY),
70+
]
71+
mock_get.assert_has_calls(expected_calls, any_order=False)
72+
73+
74+
@patch("scilog.scicat.SciCatRestAPI.get_request")
75+
@pytest.mark.parametrize(
76+
"return_options",
77+
[
78+
None,
79+
{"lazy": True},
80+
{"lazy": False},
81+
],
82+
)
83+
def test_proposals(mock_get, return_options):
84+
scicat = SciCat(ADDRESS, return_options=return_options)
85+
scicat.http_client.config = {}
86+
mock_get.side_effect = [[1, 2], [3, 4], []]
87+
proposals = [1, 2, 3, 4]
88+
scicat_proposals = scicat.proposals
89+
for i, p in enumerate(scicat_proposals):
90+
assert p == proposals[i]
91+
lazy = return_options.get("lazy", False) if return_options else False
92+
assert len(list(scicat_proposals)) == (0 if lazy else len(proposals))

0 commit comments

Comments
 (0)