airwallex-sdk 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. airwallex/__init__.py +74 -0
  2. airwallex/api/__init__.py +37 -0
  3. airwallex/api/account.py +107 -0
  4. airwallex/api/account_detail.py +469 -0
  5. airwallex/api/base.py +488 -0
  6. airwallex/api/beneficiary.py +156 -0
  7. airwallex/api/financial_transaction.py +123 -0
  8. airwallex/api/invoice.py +257 -0
  9. airwallex/api/issuing_authorization.py +313 -0
  10. airwallex/api/issuing_card.py +411 -0
  11. airwallex/api/issuing_cardholder.py +234 -0
  12. airwallex/api/issuing_config.py +80 -0
  13. airwallex/api/issuing_digital_wallet_token.py +249 -0
  14. airwallex/api/issuing_transaction.py +231 -0
  15. airwallex/api/issuing_transaction_dispute.py +339 -0
  16. airwallex/api/payment.py +148 -0
  17. airwallex/client.py +396 -0
  18. airwallex/exceptions.py +222 -0
  19. airwallex/models/__init__.py +69 -0
  20. airwallex/models/account.py +51 -0
  21. airwallex/models/account_detail.py +259 -0
  22. airwallex/models/base.py +121 -0
  23. airwallex/models/beneficiary.py +70 -0
  24. airwallex/models/financial_transaction.py +30 -0
  25. airwallex/models/fx.py +58 -0
  26. airwallex/models/invoice.py +102 -0
  27. airwallex/models/issuing_authorization.py +41 -0
  28. airwallex/models/issuing_card.py +135 -0
  29. airwallex/models/issuing_cardholder.py +52 -0
  30. airwallex/models/issuing_common.py +83 -0
  31. airwallex/models/issuing_config.py +62 -0
  32. airwallex/models/issuing_digital_wallet_token.py +38 -0
  33. airwallex/models/issuing_transaction.py +42 -0
  34. airwallex/models/issuing_transaction_dispute.py +59 -0
  35. airwallex/models/payment.py +81 -0
  36. airwallex/utils.py +107 -0
  37. airwallex_sdk-0.1.0.dist-info/METADATA +202 -0
  38. airwallex_sdk-0.1.0.dist-info/RECORD +39 -0
  39. airwallex_sdk-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,411 @@
1
+ """
2
+ Airwallex Issuing Card API.
3
+ """
4
+ from typing import Dict, Any, List, Optional, Type, TypeVar, Union, cast
5
+ from datetime import datetime
6
+ from ..models.issuing_card import Card, CardCreateRequest, CardUpdateRequest, CardDetails, CardLimits
7
+ from .base import AirwallexAPIBase
8
+
9
+ T = TypeVar("T", bound=Card)
10
+
11
+
12
+ class IssuingCard(AirwallexAPIBase[Card]):
13
+ """
14
+ Operations for Airwallex issuing cards.
15
+
16
+ Cards represent virtual or physical payment cards associated with cardholders.
17
+ """
18
+ endpoint = "issuing/cards"
19
+ model_class = cast(Type[Card], Card)
20
+
21
+ def create_card(self, card: CardCreateRequest) -> Card:
22
+ """
23
+ Create a new card.
24
+
25
+ Args:
26
+ card: CardCreateRequest model with card details
27
+
28
+ Returns:
29
+ Card: The created card
30
+ """
31
+ url = f"{self.base_path}/create"
32
+
33
+ if not self.client.__class__.__name__.startswith('Async'):
34
+ response = self.client._request("POST", url, json=card.to_api_dict())
35
+ return self.model_class.from_api_response(response.json())
36
+ else:
37
+ raise ValueError("Use create_card_async for async clients")
38
+
39
+ async def create_card_async(self, card: CardCreateRequest) -> Card:
40
+ """
41
+ Create a new card asynchronously.
42
+
43
+ Args:
44
+ card: CardCreateRequest model with card details
45
+
46
+ Returns:
47
+ Card: The created card
48
+ """
49
+ url = f"{self.base_path}/create"
50
+
51
+ if self.client.__class__.__name__.startswith('Async'):
52
+ response = await self.client._request("POST", url, json=card.to_api_dict())
53
+ return self.model_class.from_api_response(response.json())
54
+ else:
55
+ raise ValueError("Use create_card for sync clients")
56
+
57
+ def get_card_details(self, card_id: str) -> CardDetails:
58
+ """
59
+ Get sensitive card details.
60
+
61
+ Args:
62
+ card_id: The ID of the card
63
+
64
+ Returns:
65
+ CardDetails: Sensitive card details
66
+ """
67
+ url = f"{self._build_url(card_id)}/details"
68
+
69
+ if not self.client.__class__.__name__.startswith('Async'):
70
+ response = self.client._request("GET", url)
71
+ return CardDetails.from_api_response(response.json())
72
+ else:
73
+ raise ValueError("Use get_card_details_async for async clients")
74
+
75
+ async def get_card_details_async(self, card_id: str) -> CardDetails:
76
+ """
77
+ Get sensitive card details asynchronously.
78
+
79
+ Args:
80
+ card_id: The ID of the card
81
+
82
+ Returns:
83
+ CardDetails: Sensitive card details
84
+ """
85
+ url = f"{self._build_url(card_id)}/details"
86
+
87
+ if self.client.__class__.__name__.startswith('Async'):
88
+ response = await self.client._request("GET", url)
89
+ return CardDetails.from_api_response(response.json())
90
+ else:
91
+ raise ValueError("Use get_card_details for sync clients")
92
+
93
+ def activate_card(self, card_id: str) -> None:
94
+ """
95
+ Activate a physical card.
96
+
97
+ Args:
98
+ card_id: The ID of the card to activate
99
+ """
100
+ url = f"{self._build_url(card_id)}/activate"
101
+
102
+ if not self.client.__class__.__name__.startswith('Async'):
103
+ self.client._request("POST", url)
104
+ else:
105
+ raise ValueError("Use activate_card_async for async clients")
106
+
107
+ async def activate_card_async(self, card_id: str) -> None:
108
+ """
109
+ Activate a physical card asynchronously.
110
+
111
+ Args:
112
+ card_id: The ID of the card to activate
113
+ """
114
+ url = f"{self._build_url(card_id)}/activate"
115
+
116
+ if self.client.__class__.__name__.startswith('Async'):
117
+ await self.client._request("POST", url)
118
+ else:
119
+ raise ValueError("Use activate_card for sync clients")
120
+
121
+ def get_card_limits(self, card_id: str) -> CardLimits:
122
+ """
123
+ Get card remaining limits.
124
+
125
+ Args:
126
+ card_id: The ID of the card
127
+
128
+ Returns:
129
+ CardLimits: Card remaining limits
130
+ """
131
+ url = f"{self._build_url(card_id)}/limits"
132
+
133
+ if not self.client.__class__.__name__.startswith('Async'):
134
+ response = self.client._request("GET", url)
135
+ return CardLimits.from_api_response(response.json())
136
+ else:
137
+ raise ValueError("Use get_card_limits_async for async clients")
138
+
139
+ async def get_card_limits_async(self, card_id: str) -> CardLimits:
140
+ """
141
+ Get card remaining limits asynchronously.
142
+
143
+ Args:
144
+ card_id: The ID of the card
145
+
146
+ Returns:
147
+ CardLimits: Card remaining limits
148
+ """
149
+ url = f"{self._build_url(card_id)}/limits"
150
+
151
+ if self.client.__class__.__name__.startswith('Async'):
152
+ response = await self.client._request("GET", url)
153
+ return CardLimits.from_api_response(response.json())
154
+ else:
155
+ raise ValueError("Use get_card_limits for sync clients")
156
+
157
+ def update_card(self, card_id: str, update_data: CardUpdateRequest) -> Card:
158
+ """
159
+ Update a card.
160
+
161
+ Args:
162
+ card_id: The ID of the card to update
163
+ update_data: CardUpdateRequest model with update details
164
+
165
+ Returns:
166
+ Card: The updated card
167
+ """
168
+ url = f"{self._build_url(card_id)}/update"
169
+
170
+ if not self.client.__class__.__name__.startswith('Async'):
171
+ response = self.client._request("POST", url, json=update_data.to_api_dict())
172
+ return self.model_class.from_api_response(response.json())
173
+ else:
174
+ raise ValueError("Use update_card_async for async clients")
175
+
176
+ async def update_card_async(self, card_id: str, update_data: CardUpdateRequest) -> Card:
177
+ """
178
+ Update a card asynchronously.
179
+
180
+ Args:
181
+ card_id: The ID of the card to update
182
+ update_data: CardUpdateRequest model with update details
183
+
184
+ Returns:
185
+ Card: The updated card
186
+ """
187
+ url = f"{self._build_url(card_id)}/update"
188
+
189
+ if self.client.__class__.__name__.startswith('Async'):
190
+ response = await self.client._request("POST", url, json=update_data.to_api_dict())
191
+ return self.model_class.from_api_response(response.json())
192
+ else:
193
+ raise ValueError("Use update_card for sync clients")
194
+
195
+ def list_with_filters(
196
+ self,
197
+ card_status: Optional[str] = None,
198
+ cardholder_id: Optional[str] = None,
199
+ from_created_at: Optional[Union[str, datetime]] = None,
200
+ from_updated_at: Optional[Union[str, datetime]] = None,
201
+ nick_name: Optional[str] = None,
202
+ to_created_at: Optional[Union[str, datetime]] = None,
203
+ to_updated_at: Optional[Union[str, datetime]] = None,
204
+ page_num: int = 0,
205
+ page_size: int = 10
206
+ ) -> List[Card]:
207
+ """
208
+ List cards with filtering options.
209
+
210
+ Args:
211
+ card_status: Filter by status
212
+ cardholder_id: Filter by cardholder ID
213
+ from_created_at: Filter by creation date (start, inclusive)
214
+ from_updated_at: Filter by update date (start, inclusive)
215
+ nick_name: Filter by card nickname
216
+ to_created_at: Filter by creation date (end, inclusive)
217
+ to_updated_at: Filter by update date (end, inclusive)
218
+ page_num: Page number, starts from 0
219
+ page_size: Number of results per page
220
+
221
+ Returns:
222
+ List[Card]: List of matching cards
223
+ """
224
+ params = {
225
+ "page_num": page_num,
226
+ "page_size": page_size
227
+ }
228
+
229
+ if card_status:
230
+ params["card_status"] = card_status
231
+
232
+ if cardholder_id:
233
+ params["cardholder_id"] = cardholder_id
234
+
235
+ if from_created_at:
236
+ if isinstance(from_created_at, datetime):
237
+ from_created_at = from_created_at.isoformat()
238
+ params["from_created_at"] = from_created_at
239
+
240
+ if from_updated_at:
241
+ if isinstance(from_updated_at, datetime):
242
+ from_updated_at = from_updated_at.isoformat()
243
+ params["from_updated_at"] = from_updated_at
244
+
245
+ if nick_name:
246
+ params["nick_name"] = nick_name
247
+
248
+ if to_created_at:
249
+ if isinstance(to_created_at, datetime):
250
+ to_created_at = to_created_at.isoformat()
251
+ params["to_created_at"] = to_created_at
252
+
253
+ if to_updated_at:
254
+ if isinstance(to_updated_at, datetime):
255
+ to_updated_at = to_updated_at.isoformat()
256
+ params["to_updated_at"] = to_updated_at
257
+
258
+ if not self.client.__class__.__name__.startswith('Async'):
259
+ response = self.client._request("GET", self._build_url(), params=params)
260
+ data = response.json()
261
+ return [self.model_class.from_api_response(item) for item in data.get("items", [])]
262
+ else:
263
+ raise ValueError("Use list_with_filters_async for async clients")
264
+
265
+ async def list_with_filters_async(
266
+ self,
267
+ card_status: Optional[str] = None,
268
+ cardholder_id: Optional[str] = None,
269
+ from_created_at: Optional[Union[str, datetime]] = None,
270
+ from_updated_at: Optional[Union[str, datetime]] = None,
271
+ nick_name: Optional[str] = None,
272
+ to_created_at: Optional[Union[str, datetime]] = None,
273
+ to_updated_at: Optional[Union[str, datetime]] = None,
274
+ page_num: int = 0,
275
+ page_size: int = 10
276
+ ) -> List[Card]:
277
+ """
278
+ List cards with filtering options asynchronously.
279
+
280
+ Args:
281
+ card_status: Filter by status
282
+ cardholder_id: Filter by cardholder ID
283
+ from_created_at: Filter by creation date (start, inclusive)
284
+ from_updated_at: Filter by update date (start, inclusive)
285
+ nick_name: Filter by card nickname
286
+ to_created_at: Filter by creation date (end, inclusive)
287
+ to_updated_at: Filter by update date (end, inclusive)
288
+ page_num: Page number, starts from 0
289
+ page_size: Number of results per page
290
+
291
+ Returns:
292
+ List[Card]: List of matching cards
293
+ """
294
+ params = {
295
+ "page_num": page_num,
296
+ "page_size": page_size
297
+ }
298
+
299
+ if card_status:
300
+ params["card_status"] = card_status
301
+
302
+ if cardholder_id:
303
+ params["cardholder_id"] = cardholder_id
304
+
305
+ if from_created_at:
306
+ if isinstance(from_created_at, datetime):
307
+ from_created_at = from_created_at.isoformat()
308
+ params["from_created_at"] = from_created_at
309
+
310
+ if from_updated_at:
311
+ if isinstance(from_updated_at, datetime):
312
+ from_updated_at = from_updated_at.isoformat()
313
+ params["from_updated_at"] = from_updated_at
314
+
315
+ if nick_name:
316
+ params["nick_name"] = nick_name
317
+
318
+ if to_created_at:
319
+ if isinstance(to_created_at, datetime):
320
+ to_created_at = to_created_at.isoformat()
321
+ params["to_created_at"] = to_created_at
322
+
323
+ if to_updated_at:
324
+ if isinstance(to_updated_at, datetime):
325
+ to_updated_at = to_updated_at.isoformat()
326
+ params["to_updated_at"] = to_updated_at
327
+
328
+ if self.client.__class__.__name__.startswith('Async'):
329
+ response = await self.client._request("GET", self._build_url(), params=params)
330
+ data = response.json()
331
+ return [self.model_class.from_api_response(item) for item in data.get("items", [])]
332
+ else:
333
+ raise ValueError("Use list_with_filters for sync clients")
334
+
335
+ def paginate(self, **params: Any) -> List[Card]:
336
+ """
337
+ Fetch all pages of cards.
338
+
339
+ Args:
340
+ **params: Filter parameters to pass to the API
341
+
342
+ Returns:
343
+ List[Card]: All cards matching the filters
344
+ """
345
+ if str(self.client.__class__.__name__).startswith('Async'):
346
+ raise ValueError("This method requires a sync client.")
347
+
348
+ all_items: List[Dict[str, Any]] = []
349
+ page_num = params.get("page_num", 0)
350
+ page_size = params.get("page_size", 10)
351
+
352
+ while True:
353
+ params["page_num"] = page_num
354
+ params["page_size"] = page_size
355
+
356
+ response = self.client._request("GET", self._build_url(), params=params)
357
+ data = response.json()
358
+
359
+ items = data.get("items", [])
360
+ has_more = data.get("has_more", False)
361
+
362
+ if not items:
363
+ break
364
+
365
+ all_items.extend(items)
366
+
367
+ if not has_more:
368
+ break
369
+
370
+ page_num += 1
371
+
372
+ return [self.model_class.from_api_response(item) for item in all_items]
373
+
374
+ async def paginate_async(self, **params: Any) -> List[Card]:
375
+ """
376
+ Fetch all pages of cards asynchronously.
377
+
378
+ Args:
379
+ **params: Filter parameters to pass to the API
380
+
381
+ Returns:
382
+ List[Card]: All cards matching the filters
383
+ """
384
+ if not self.client.__class__.__name__.startswith('Async'):
385
+ raise ValueError("This method requires an async client.")
386
+
387
+ all_items: List[Dict[str, Any]] = []
388
+ page_num = params.get("page_num", 0)
389
+ page_size = params.get("page_size", 10)
390
+
391
+ while True:
392
+ params["page_num"] = page_num
393
+ params["page_size"] = page_size
394
+
395
+ response = await self.client._request("GET", self._build_url(), params=params)
396
+ data = response.json()
397
+
398
+ items = data.get("items", [])
399
+ has_more = data.get("has_more", False)
400
+
401
+ if not items:
402
+ break
403
+
404
+ all_items.extend(items)
405
+
406
+ if not has_more:
407
+ break
408
+
409
+ page_num += 1
410
+
411
+ return [self.model_class.from_api_response(item) for item in all_items]
@@ -0,0 +1,234 @@
1
+ """
2
+ Airwallex Issuing Cardholder API.
3
+ """
4
+ from typing import Dict, Any, List, Optional, Type, TypeVar, Union, cast
5
+ from ..models.issuing_cardholder import Cardholder, CardholderCreateRequest, CardholderUpdateRequest
6
+ from .base import AirwallexAPIBase
7
+
8
+ T = TypeVar("T", bound=Cardholder)
9
+
10
+
11
+ class IssuingCardholder(AirwallexAPIBase[Cardholder]):
12
+ """
13
+ Operations for Airwallex issuing cardholders.
14
+
15
+ Cardholders are authorized representatives that can be issued cards.
16
+ """
17
+ endpoint = "issuing/cardholders"
18
+ model_class = cast(Type[Cardholder], Cardholder)
19
+
20
+ def create_cardholder(self, cardholder: CardholderCreateRequest) -> Cardholder:
21
+ """
22
+ Create a new cardholder.
23
+
24
+ Args:
25
+ cardholder: CardholderCreateRequest model with cardholder details
26
+
27
+ Returns:
28
+ Cardholder: The created cardholder
29
+ """
30
+ url = f"{self.base_path}/create"
31
+
32
+ if not self.client.__class__.__name__.startswith('Async'):
33
+ response = self.client._request("POST", url, json=cardholder.to_api_dict())
34
+ return self.model_class.from_api_response(response.json())
35
+ else:
36
+ raise ValueError("Use create_cardholder_async for async clients")
37
+
38
+ async def create_cardholder_async(self, cardholder: CardholderCreateRequest) -> Cardholder:
39
+ """
40
+ Create a new cardholder asynchronously.
41
+
42
+ Args:
43
+ cardholder: CardholderCreateRequest model with cardholder details
44
+
45
+ Returns:
46
+ Cardholder: The created cardholder
47
+ """
48
+ url = f"{self.base_path}/create"
49
+
50
+ if self.client.__class__.__name__.startswith('Async'):
51
+ response = await self.client._request("POST", url, json=cardholder.to_api_dict())
52
+ return self.model_class.from_api_response(response.json())
53
+ else:
54
+ raise ValueError("Use create_cardholder for sync clients")
55
+
56
+ def list_with_filters(
57
+ self,
58
+ cardholder_status: Optional[str] = None,
59
+ page_num: int = 0,
60
+ page_size: int = 10
61
+ ) -> List[Cardholder]:
62
+ """
63
+ List cardholders with filtering options.
64
+
65
+ Args:
66
+ cardholder_status: Filter by status (PENDING, READY, INCOMPLETE, DISABLED)
67
+ page_num: Page number, starts from 0
68
+ page_size: Number of results per page
69
+
70
+ Returns:
71
+ List[Cardholder]: List of matching cardholders
72
+ """
73
+ params = {
74
+ "page_num": page_num,
75
+ "page_size": page_size
76
+ }
77
+
78
+ if cardholder_status:
79
+ params["cardholder_status"] = cardholder_status
80
+
81
+ if not self.client.__class__.__name__.startswith('Async'):
82
+ response = self.client._request("GET", self._build_url(), params=params)
83
+ data = response.json()
84
+ return [self.model_class.from_api_response(item) for item in data.get("items", [])]
85
+ else:
86
+ raise ValueError("Use list_with_filters_async for async clients")
87
+
88
+ async def list_with_filters_async(
89
+ self,
90
+ cardholder_status: Optional[str] = None,
91
+ page_num: int = 0,
92
+ page_size: int = 10
93
+ ) -> List[Cardholder]:
94
+ """
95
+ List cardholders with filtering options asynchronously.
96
+
97
+ Args:
98
+ cardholder_status: Filter by status (PENDING, READY, INCOMPLETE, DISABLED)
99
+ page_num: Page number, starts from 0
100
+ page_size: Number of results per page
101
+
102
+ Returns:
103
+ List[Cardholder]: List of matching cardholders
104
+ """
105
+ params = {
106
+ "page_num": page_num,
107
+ "page_size": page_size
108
+ }
109
+
110
+ if cardholder_status:
111
+ params["cardholder_status"] = cardholder_status
112
+
113
+ if self.client.__class__.__name__.startswith('Async'):
114
+ response = await self.client._request("GET", self._build_url(), params=params)
115
+ data = response.json()
116
+ return [self.model_class.from_api_response(item) for item in data.get("items", [])]
117
+ else:
118
+ raise ValueError("Use list_with_filters for sync clients")
119
+
120
+ def update_cardholder(self, cardholder_id: str, update_data: CardholderUpdateRequest) -> Cardholder:
121
+ """
122
+ Update a cardholder.
123
+
124
+ Args:
125
+ cardholder_id: The ID of the cardholder to update
126
+ update_data: CardholderUpdateRequest model with update details
127
+
128
+ Returns:
129
+ Cardholder: The updated cardholder
130
+ """
131
+ url = f"{self._build_url(cardholder_id)}/update"
132
+
133
+ if not self.client.__class__.__name__.startswith('Async'):
134
+ response = self.client._request("POST", url, json=update_data.to_api_dict())
135
+ return self.model_class.from_api_response(response.json())
136
+ else:
137
+ raise ValueError("Use update_cardholder_async for async clients")
138
+
139
+ async def update_cardholder_async(self, cardholder_id: str, update_data: CardholderUpdateRequest) -> Cardholder:
140
+ """
141
+ Update a cardholder asynchronously.
142
+
143
+ Args:
144
+ cardholder_id: The ID of the cardholder to update
145
+ update_data: CardholderUpdateRequest model with update details
146
+
147
+ Returns:
148
+ Cardholder: The updated cardholder
149
+ """
150
+ url = f"{self._build_url(cardholder_id)}/update"
151
+
152
+ if self.client.__class__.__name__.startswith('Async'):
153
+ response = await self.client._request("POST", url, json=update_data.to_api_dict())
154
+ return self.model_class.from_api_response(response.json())
155
+ else:
156
+ raise ValueError("Use update_cardholder for sync clients")
157
+
158
+ def paginate(self, **params: Any) -> List[Cardholder]:
159
+ """
160
+ Fetch all pages of cardholders.
161
+
162
+ Args:
163
+ **params: Filter parameters to pass to the API
164
+
165
+ Returns:
166
+ List[Cardholder]: All cardholders matching the filters
167
+ """
168
+ if str(self.client.__class__.__name__).startswith('Async'):
169
+ raise ValueError("This method requires a sync client.")
170
+
171
+ all_items: List[Dict[str, Any]] = []
172
+ page_num = params.get("page_num", 0)
173
+ page_size = params.get("page_size", 10)
174
+
175
+ while True:
176
+ params["page_num"] = page_num
177
+ params["page_size"] = page_size
178
+
179
+ response = self.client._request("GET", self._build_url(), params=params)
180
+ data = response.json()
181
+
182
+ items = data.get("items", [])
183
+ has_more = data.get("has_more", False)
184
+
185
+ if not items:
186
+ break
187
+
188
+ all_items.extend(items)
189
+
190
+ if not has_more:
191
+ break
192
+
193
+ page_num += 1
194
+
195
+ return [self.model_class.from_api_response(item) for item in all_items]
196
+
197
+ async def paginate_async(self, **params: Any) -> List[Cardholder]:
198
+ """
199
+ Fetch all pages of cardholders asynchronously.
200
+
201
+ Args:
202
+ **params: Filter parameters to pass to the API
203
+
204
+ Returns:
205
+ List[Cardholder]: All cardholders matching the filters
206
+ """
207
+ if not self.client.__class__.__name__.startswith('Async'):
208
+ raise ValueError("This method requires an async client.")
209
+
210
+ all_items: List[Dict[str, Any]] = []
211
+ page_num = params.get("page_num", 0)
212
+ page_size = params.get("page_size", 10)
213
+
214
+ while True:
215
+ params["page_num"] = page_num
216
+ params["page_size"] = page_size
217
+
218
+ response = await self.client._request("GET", self._build_url(), params=params)
219
+ data = response.json()
220
+
221
+ items = data.get("items", [])
222
+ has_more = data.get("has_more", False)
223
+
224
+ if not items:
225
+ break
226
+
227
+ all_items.extend(items)
228
+
229
+ if not has_more:
230
+ break
231
+
232
+ page_num += 1
233
+
234
+ return [self.model_class.from_api_response(item) for item in all_items]