Skip to content

Commit 3879c32

Browse files
basic pagination + sorting
1 parent a08a927 commit 3879c32

5 files changed

Lines changed: 90 additions & 10 deletions

File tree

mp_api/client/core/client.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,10 @@ def _submit_requests( # noqa
965965
# No splitting needed - get first page
966966
total_data = {"data": []}
967967
initial_criteria = copy(criteria)
968+
if isinstance(
969+
initial_criteria.get("_page"), int
970+
) and not initial_criteria.get("_per_page"):
971+
initial_criteria["_per_page"] = initial_criteria.get("_limit")
968972
data, total_num_docs = self._submit_request_and_process(
969973
url=url,
970974
verify=True,
@@ -1438,6 +1442,9 @@ def _search(
14381442
# This method should be customized for each end point to give more user friendly,
14391443
# documented kwargs.
14401444

1445+
# If user specifies page, ensure only one chunk is returned
1446+
if isinstance(kwargs.get("_page"), int) and num_chunks is None:
1447+
num_chunks = 1
14411448
return self._get_all_documents(
14421449
kwargs,
14431450
all_fields=all_fields,

mp_api/client/routes/materials/electrodes.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class BaseElectrodeRester(BaseRester):
1515
primary_key = "battery_id"
1616
_exclude_search_fields: list[str] | None = None
1717

18-
def search( # pragma: ignore
18+
def search(
1919
self,
2020
battery_ids: str | list[str] | None = None,
2121
average_voltage: tuple[float, float] | None = None,
@@ -39,6 +39,8 @@ def search( # pragma: ignore
3939
chunk_size: int = 1000,
4040
all_fields: bool = True,
4141
fields: list[str] | None = None,
42+
_page: int | None = None,
43+
_sort_fields: str | None = None,
4244
) -> list[InsertionElectrodeDoc | ConversionElectrodeDoc] | list[dict]:
4345
"""Query using a variety of search criteria.
4446
@@ -77,6 +79,8 @@ def search( # pragma: ignore
7779
all_fields (bool): Whether to return all fields in the document. Defaults to True.
7880
fields (List[str]): List of fields in InsertionElectrodeDoc or ConversionElectrodeDoc to return data for.
7981
Default is battery_id and last_updated if all_fields is False.
82+
_page (int or None) : Page of the results to skip to.
83+
_sort_fields (str or None) : Field to sort on. Including a leading "-" sign will reverse sort order.
8084
8185
Returns:
8286
([InsertionElectrodeDoc or ConversionElectrodeDoc], [dict]) List of insertion/conversion electrode documents or dictionaries.

mp_api/client/routes/materials/summary.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ def search( # noqa: D417
7373
chunk_size: int = 1000,
7474
all_fields: bool = True,
7575
fields: list[str] | None = None,
76+
_page: int | None = None,
77+
_sort_fields: str | None = None,
7678
**kwargs,
7779
) -> list[SummaryDoc] | list[dict]:
7880
"""Query core data using a variety of search criteria.
@@ -150,6 +152,8 @@ def search( # noqa: D417
150152
all_fields (bool): Whether to return all fields in the document. Defaults to True.
151153
fields (List[str]): List of fields in SummaryDoc to return data for.
152154
Default is material_id if all_fields is False.
155+
_page (int or None) : Page of the results to skip to.
156+
_sort_fields (str or None) : Field to sort on. Including a leading "-" sign will reverse sort order.
153157
154158
Returns:
155159
([SummaryDoc], [dict]) List of SummaryDoc documents or dictionaries.
@@ -181,6 +185,8 @@ def search( # noqa: D417
181185
"weighted_surface_energy",
182186
"weighted_work_function",
183187
"shape_factor",
188+
"_page",
189+
"_sort_fields",
184190
]
185191

186192
min_max_name_dict = {
@@ -284,14 +290,17 @@ def _csrc(x):
284290
)
285291

286292
for param, value in user_settings.items():
287-
if isinstance(value, (int, float)):
288-
value = (value, value)
289-
query_params.update(
290-
{
291-
f"{min_max_name_dict[param]}_min": value[0],
292-
f"{min_max_name_dict[param]}_max": value[1],
293-
}
294-
)
293+
if param.startswith("_"):
294+
query_params[param] = value
295+
else:
296+
if isinstance(value, (int, float)):
297+
value = (value, value)
298+
query_params.update(
299+
{
300+
f"{min_max_name_dict[param]}_min": value[0],
301+
f"{min_max_name_dict[param]}_max": value[1],
302+
}
303+
)
295304

296305
if material_ids:
297306
if isinstance(material_ids, str):

tests/client/materials/test_electrodes.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ def conversion_rester():
3030
"num_chunks",
3131
"all_fields",
3232
"fields",
33+
"_page",
34+
"_sort_fields",
3335
]
3436

3537
sub_doc_fields: list = []
@@ -80,3 +82,31 @@ def test_conversion_client(conversion_rester):
8082
},
8183
sub_doc_fields=sub_doc_fields,
8284
)
85+
86+
87+
@pytest.mark.xfail(reason="sort requires API redeployment", strict=False)
88+
@requires_api_key
89+
def test_pagination_sort():
90+
num_docs = 5
91+
with ElectrodeRester() as rester:
92+
results_page_1 = rester.search(_page=1, chunk_size=num_docs)
93+
results_page_2 = rester.search(_page=2, chunk_size=num_docs)
94+
assert all(
95+
len(results) == num_docs for results in (results_page_1, results_page_2)
96+
)
97+
assert {doc.battery_id for doc in results_page_1}.intersection(
98+
{doc.battery_id for doc in results_page_2}
99+
) == set()
100+
101+
ascending_e_hull = rester.search(_page=1, _sort_fields="average_voltage")
102+
descending_e_hull = rester.search(_page=1, _sort_fields="-average_voltage")
103+
104+
assert sorted(
105+
range(num_docs), key=lambda idx: ascending_e_hull[idx].average_voltage
106+
) == list(range(num_docs))
107+
108+
assert sorted(
109+
range(num_docs),
110+
key=lambda idx: descending_e_hull[idx].average_voltage,
111+
reverse=True,
112+
) == list(range(num_docs))

tests/client/materials/test_summary.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import os
22
from ..conftest import client_search_testing, requires_api_key
33

4-
import pytest
54
from emmet.core.summary import HasProps
65
from emmet.core.symmetry import CrystalSystem
6+
import numpy as np
77
from pymatgen.analysis.magnetism import Ordering
8+
import pytest
89

910
from mp_api.client.routes.materials.summary import SummaryRester
1011
from mp_api.client.core.exceptions import MPRestWarning, MPRestError
@@ -16,6 +17,8 @@
1617
"num_chunks",
1718
"all_fields",
1819
"fields",
20+
"_page",
21+
"_sort_fields",
1922
]
2023

2124
alt_name_dict: dict = {
@@ -134,3 +137,30 @@ def test_warning_messages():
134137

135138
with pytest.raises(MPRestError, match="not a valid property"):
136139
_ = search_method(num_elements=10, has_props=["apples"])
140+
141+
142+
@requires_api_key
143+
def test_pagination_sort():
144+
num_docs = 5
145+
with SummaryRester() as rester:
146+
results_page_1 = rester.search(_page=1, chunk_size=num_docs)
147+
results_page_2 = rester.search(_page=2, chunk_size=num_docs)
148+
assert all(
149+
len(results) == num_docs for results in (results_page_1, results_page_2)
150+
)
151+
assert {doc.material_id for doc in results_page_1}.intersection(
152+
{doc.material_id for doc in results_page_2}
153+
) == set()
154+
155+
ascending_e_hull = rester.search(_page=1, _sort_fields="energy_above_hull")
156+
descending_e_hull = rester.search(_page=1, _sort_fields="-energy_above_hull")
157+
158+
assert sorted(
159+
range(num_docs), key=lambda idx: ascending_e_hull[idx].energy_above_hull
160+
) == list(range(num_docs))
161+
162+
assert sorted(
163+
range(num_docs),
164+
key=lambda idx: descending_e_hull[idx].energy_above_hull,
165+
reverse=True,
166+
) == list(range(num_docs))

0 commit comments

Comments
 (0)