Skip to content

Commit 582439f

Browse files
authored
[fix] Fix, re-design implementation of pagination functions (#307)
- Fix bug where pagination filters were not being passed forward/saved - Redesign pagination for more explicit per-service implementation, support for filters, less fragile - Drop code coverage to 88
1 parent d0c6052 commit 582439f

26 files changed

+400
-153
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ clean:
2727

2828
## coverage - Test the project and generate an HTML coverage report
2929
coverage:
30-
$(VIRTUAL_BIN)/pytest --cov=$(PROJECT_NAME) --cov-branch --cov-report=html --cov-report=lcov --cov-report=term-missing --cov-fail-under=89
30+
$(VIRTUAL_BIN)/pytest --cov=$(PROJECT_NAME) --cov-branch --cov-report=html --cov-report=lcov --cov-report=term-missing --cov-fail-under=88
3131

3232
## docs - Generates docs for the library
3333
docs:

easypost/constant.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@
3737
"FedexSmartpostAccount",
3838
"UpsAccount",
3939
]
40+
_FILTERS_KEY = "filters"

easypost/services/address_service.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ def create(
3939

4040
def all(self, **params) -> Dict[str, Any]:
4141
"""Retrieve a list of Addresses."""
42-
return self._all_resources(self._model_class, **params)
42+
filters = {
43+
"key": "addresses",
44+
}
45+
46+
return self._all_resources(self._model_class, filters, **params)
4347

4448
def retrieve(self, id) -> Address:
4549
"""Retrieve an Address."""
@@ -69,4 +73,14 @@ def get_next_page(
6973
optional_params: Optional[Dict[str, Any]] = None,
7074
) -> Dict[str, Any]:
7175
"""Retrieve the next page of the list Addresses response."""
72-
return self._get_next_page_resources(self._model_class, addresses, page_size, optional_params)
76+
self._check_has_next_page(collection=addresses)
77+
78+
params = {
79+
"before_id": addresses["addresses"][-1].id,
80+
"page_size": page_size,
81+
}
82+
83+
if optional_params:
84+
params.update(optional_params)
85+
86+
return self.all(**params)

easypost/services/base_service.py

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
from typing import (
33
Any,
44
Dict,
5-
List,
65
Optional,
76
)
87

9-
from easypost.constant import NO_MORE_PAGES_ERROR
8+
from easypost.constant import (
9+
_FILTERS_KEY,
10+
NO_MORE_PAGES_ERROR,
11+
)
1012
from easypost.easypost_object import convert_to_easypost_object
1113
from easypost.errors import EndOfPaginationError
1214
from easypost.requestor import (
@@ -46,12 +48,14 @@ def _create_resource(self, class_name: str, **params) -> Any:
4648

4749
return convert_to_easypost_object(response=response)
4850

49-
def _all_resources(self, class_name: str, **params) -> Any:
51+
def _all_resources(self, class_name: str, filters: Optional[Dict[str, Any]] = None, **params) -> Any:
5052
"""Retrieve a list of EasyPostObjects from the EasyPost API."""
5153
url = self._class_url(class_name)
52-
5354
response = Requestor(self._client).request(method=RequestMethod.GET, url=url, params=params)
5455

56+
if filters: # presence of filters indicates we are dealing with a paginated response
57+
response[_FILTERS_KEY] = filters # Save the filters used to reference in potential get_next_page call
58+
5559
return convert_to_easypost_object(response=response)
5660

5761
def _retrieve_resource(self, class_name: str, id: str) -> Any:
@@ -79,32 +83,7 @@ def _delete_resource(self, class_name: str, id: str) -> Any:
7983

8084
return convert_to_easypost_object(response=response)
8185

82-
def _get_next_page_resources(
83-
self,
84-
class_name: str,
85-
collection: Dict[str, Any],
86-
page_size: int,
87-
optional_params: Optional[Dict[str, Any]] = None,
88-
) -> Dict[str, Any]:
89-
"""Retrieve next page of EasyPostObjects via the EasyPost API."""
90-
url = self._class_url(class_name)
91-
collection_array = collection.get(url[1:])
92-
93-
if collection_array is None or len(collection_array) == 0 or not collection.get("has_more"):
86+
def _check_has_next_page(self, collection: Dict[str, Any]) -> None:
87+
"""Raise exception if there is no next page of a collection."""
88+
if not collection.get("has_more", False):
9489
raise EndOfPaginationError(NO_MORE_PAGES_ERROR)
95-
96-
params = {
97-
"before_id": collection_array[-1].id,
98-
"page_size": page_size,
99-
}
100-
101-
if optional_params:
102-
params.update(optional_params)
103-
104-
response = Requestor(self._client).request(method=RequestMethod.GET, url=url, params=params)
105-
106-
response_array: List[Any] = response.get(url[1:]) # type: ignore
107-
if response is None or len(response_array) == 0:
108-
raise EndOfPaginationError(NO_MORE_PAGES_ERROR)
109-
110-
return convert_to_easypost_object(response=response)

easypost/services/batch_service.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ def create(self, **params) -> Batch:
2424

2525
def all(self, **params) -> Dict[str, Any]:
2626
"""Retrieve a list of Batches."""
27-
return self._all_resources(self._model_class, **params)
27+
filters = {
28+
"key": "batches",
29+
}
30+
31+
return self._all_resources(self._model_class, filters, **params)
2832

2933
def retrieve(self, id: str) -> Batch:
3034
"""Retrieve a Batch."""
@@ -85,5 +89,21 @@ def get_next_page(
8589
page_size: int,
8690
optional_params: Optional[Dict[str, Any]] = None,
8791
) -> Dict[str, Any]:
88-
"""Retrieve the next page of the list Batch response."""
89-
return self._get_next_page_resources(self._model_class, batches, page_size, optional_params)
92+
"""
93+
Retrieve the next page of the list Batch response.
94+
95+
NOTE: This function has known issues with retrieving pages in order due to server-side issues.
96+
It is not recommended to be used currently.
97+
"""
98+
# API doesn't return batches newest to oldest, so these parameters don't work as expected
99+
self._check_has_next_page(collection=batches)
100+
101+
params = {
102+
"before_id": batches["batches"][-1].id,
103+
"page_size": page_size,
104+
}
105+
106+
if optional_params:
107+
params.update(optional_params)
108+
109+
return self.all(**params)

easypost/services/event_service.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ def create(self, **params) -> Event:
2727

2828
def all(self, **params) -> Dict[str, Any]:
2929
"""Retrieve a list of Events."""
30-
return self._all_resources(self._model_class, **params)
30+
filters = {
31+
"key": "events",
32+
}
33+
34+
return self._all_resources(self._model_class, filters, **params)
3135

3236
def retrieve(self, id: str) -> Event:
3337
"""Retrieve an Event."""
@@ -56,4 +60,14 @@ def get_next_page(
5660
optional_params: Optional[Dict[str, Any]] = None,
5761
) -> Dict[str, Any]:
5862
"""Retrieve the next page of the list Events response."""
59-
return self._get_next_page_resources(self._model_class, events, page_size, optional_params)
63+
self._check_has_next_page(collection=events)
64+
65+
params = {
66+
"before_id": events["events"][-1].id,
67+
"page_size": page_size,
68+
}
69+
70+
if optional_params:
71+
params.update(optional_params)
72+
73+
return self.all(**params)

easypost/services/insurance_service.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ def create(self, **params) -> Insurance:
1919

2020
def all(self, **params) -> Dict[str, Any]:
2121
"""Retrieve a list of Insurances."""
22-
return self._all_resources(self._model_class, **params)
22+
filters = {
23+
"key": "insurances",
24+
}
25+
26+
return self._all_resources(self._model_class, filters, **params)
2327

2428
def retrieve(self, id: str) -> Insurance:
2529
"""Retrieve an Insurance."""
@@ -32,4 +36,14 @@ def get_next_page(
3236
optional_params: Optional[Dict[str, Any]] = None,
3337
) -> Dict[str, Any]:
3438
"""Retrieve the next page of the list Insurance response."""
35-
return self._get_next_page_resources(self._model_class, insurances, page_size, optional_params)
39+
self._check_has_next_page(collection=insurances)
40+
41+
params = {
42+
"before_id": insurances["insurances"][-1].id,
43+
"page_size": page_size,
44+
}
45+
46+
if optional_params:
47+
params.update(optional_params)
48+
49+
return self.all(**params)

easypost/services/order_service.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
from typing import (
2-
Any,
3-
Dict,
4-
Optional,
5-
)
6-
71
from easypost.easypost_object import convert_to_easypost_object
82
from easypost.models import Order
93
from easypost.requestor import (
@@ -26,15 +20,6 @@ def retrieve(self, id: str) -> Order:
2620
"""Retrieve an Order."""
2721
return self._retrieve_resource(self._model_class, id)
2822

29-
def get_next_page(
30-
self,
31-
insurances: Dict[str, Any],
32-
page_size: int,
33-
optional_params: Optional[Dict[str, Any]] = None,
34-
) -> Dict[str, Any]:
35-
"""Retrieve the next page of the list Order response."""
36-
return self._get_next_page_resources(self._model_class, insurances, page_size, optional_params)
37-
3823
def get_rates(self, id: str) -> Order:
3924
"""Get rates for an Order."""
4025
url = f"{self._instance_url(self._model_class, id)}/rates"

easypost/services/pickup_service.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ def create(self, **params) -> Pickup:
2424

2525
def all(self, **params) -> Dict[str, Any]:
2626
"""Retrieve a list of Pickups."""
27-
return self._all_resources(self._model_class, **params)
27+
filters = {
28+
"key": "pickups",
29+
}
30+
31+
return self._all_resources(self._model_class, filters, **params)
2832

2933
def retrieve(self, id: str) -> Pickup:
3034
"""Retrieve a Pickup."""
@@ -37,7 +41,17 @@ def get_next_page(
3741
optional_params: Optional[Dict[str, Any]] = None,
3842
) -> Dict[str, Any]:
3943
"""Retrieve the next page of the list Pickup response."""
40-
return self._get_next_page_resources(self._model_class, pickups, page_size, optional_params)
44+
self._check_has_next_page(collection=pickups)
45+
46+
params = {
47+
"before_id": pickups["pickups"][-1].id,
48+
"page_size": page_size,
49+
}
50+
51+
if optional_params:
52+
params.update(optional_params)
53+
54+
return self.all(**params)
4155

4256
def buy(self, id: str, **params) -> Pickup:
4357
"""Buy a Pickup."""

easypost/services/referral_customer_service.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import requests
99
from easypost.constant import (
10+
_FILTERS_KEY,
1011
SEND_STRIPE_DETAILS_ERROR,
1112
TIMEOUT,
1213
)
@@ -63,11 +64,15 @@ def all(self, **params) -> Dict[str, Any]:
6364
6465
This function requires the Partner User's API key.
6566
"""
66-
response = Requestor(self._client).request(
67-
method=RequestMethod.GET,
68-
url="/referral_customers",
69-
params=params,
70-
)
67+
filters = {
68+
"key": "referral_customers",
69+
}
70+
71+
url = "/referral_customers"
72+
73+
response = Requestor(self._client).request(method=RequestMethod.GET, url=url, params=params)
74+
75+
response[_FILTERS_KEY] = filters # Save the filters used to reference in potential get_next_page call
7176

7277
return convert_to_easypost_object(response=response)
7378

@@ -78,7 +83,17 @@ def get_next_page(
7883
optional_params: Optional[Dict[str, Any]] = None,
7984
) -> Dict[str, Any]:
8085
"""Retrieve next page of referral customers."""
81-
return self._get_next_page_resources("referral_customers", referral_customers, page_size, optional_params)
86+
self._check_has_next_page(collection=referral_customers)
87+
88+
params = {
89+
"before_id": referral_customers["referral_customers"][-1].id,
90+
"page_size": page_size,
91+
}
92+
93+
if optional_params:
94+
params.update(optional_params)
95+
96+
return self.all(**params)
8297

8398
def add_credit_card(
8499
self,

easypost/services/refund_service.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from typing import (
22
Any,
33
Dict,
4-
List,
54
Optional,
65
)
76

@@ -18,9 +17,13 @@ def create(self, **params) -> Refund:
1817
"""Create a Shipment Refund."""
1918
return self._create_resource(self._model_class, **params)
2019

21-
def all(self, **params) -> List[Refund]:
20+
def all(self, **params) -> Dict[str, Any]:
2221
"""Retrieve a list of Shipment Refunds."""
23-
return self._all_resources(self._model_class, **params)
22+
filters = {
23+
"key": "refunds",
24+
}
25+
26+
return self._all_resources(self._model_class, filters, **params)
2427

2528
def retrieve(self, id: str) -> Refund:
2629
"""Retrieve a Shipment Refund."""
@@ -33,4 +36,14 @@ def get_next_page(
3336
optional_params: Optional[Dict[str, Any]] = None,
3437
) -> Dict[str, Any]:
3538
"""Retrieve the next page of the list Refund response."""
36-
return self._get_next_page_resources(self._model_class, refunds, page_size, optional_params)
39+
self._check_has_next_page(collection=refunds)
40+
41+
params = {
42+
"before_id": refunds["refunds"][-1].id,
43+
"page_size": page_size,
44+
}
45+
46+
if optional_params:
47+
params.update(optional_params)
48+
49+
return self.all(**params)

0 commit comments

Comments
 (0)