Skip to content

Commit 40521ae

Browse files
committed
refactor: refactored models, updated unit test, and added parameter in API call
1 parent 210f58e commit 40521ae

11 files changed

+70
-70
lines changed

sinch/core/pagination.py

+3-11
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ async def _initialize(cls, sinch, endpoint):
121121
return cls(sinch, endpoint, result)
122122

123123

124-
class TokenBasedPaginatorBase(Paginator):
124+
class TokenBasedPaginator(Paginator):
125125
"""Base paginator for token-based pagination."""
126126

127127
def __init__(self, sinch, endpoint, yield_first_page=False, result=None):
@@ -132,9 +132,6 @@ def __init__(self, sinch, endpoint, yield_first_page=False, result=None):
132132
self.result = result or self._sinch.configuration.transport.request(self.endpoint)
133133
self.has_next_page = bool(self.result.next_page_token)
134134

135-
def __repr__(self):
136-
pass
137-
138135
def _calculate_next_page(self):
139136
self.has_next_page = bool(self.result.next_page_token)
140137

@@ -156,12 +153,7 @@ def _initialize(cls, sinch, endpoint):
156153
return cls(sinch, endpoint, yield_first_page=False, result=result)
157154

158155

159-
class TokenBasedPaginator(TokenBasedPaginatorBase):
160-
"""Paginator that skips the first page."""
161-
pass
162-
163-
164-
class TokenBasedPaginatorNumbers(TokenBasedPaginatorBase):
156+
class TokenBasedPaginatorNumbers(TokenBasedPaginator):
165157
"""
166158
Paginator for handling token-based pagination specifically for phone numbers.
167159
@@ -231,7 +223,7 @@ def wrapper():
231223
return wrapper
232224

233225

234-
class AsyncTokenBasedPaginator(TokenBasedPaginatorBase):
226+
class AsyncTokenBasedPaginator(TokenBasedPaginator):
235227
"""Asynchronous token-based paginator."""
236228

237229
async def next_page(self):

sinch/domains/numbers/active_numbers.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
UpdateNumberConfigurationResponse, GetNumberConfigurationResponse, ReleaseNumberFromProjectResponse
1717
)
1818
from sinch.domains.numbers.models.numbers import (
19-
NumberTypeValues, CapabilityTypeValues, NumberSearchPatternTypeValues, OrderByValues
19+
CapabilityTypeValuesList, NumberTypeValues, NumberSearchPatternTypeValues, OrderByValues
2020
)
2121

2222

@@ -28,7 +28,7 @@ def list(
2828
number_type: NumberTypeValues,
2929
number_pattern: Optional[StrictStr] = None,
3030
number_search_pattern: Optional[NumberSearchPatternTypeValues] = None,
31-
capabilities: Optional[CapabilityTypeValues] = None,
31+
capability: Optional[CapabilityTypeValuesList] = None,
3232
page_size: Optional[StrictInt] = None,
3333
page_token: Optional[StrictStr] = None,
3434
order_by: Optional[OrderByValues] = None,
@@ -43,7 +43,7 @@ def list(
4343
number_pattern (Optional[StrictStr]): Specific sequence of digits to search for.
4444
number_search_pattern (Optional[NumberSearchPatternType]):
4545
Pattern to apply (e.g., "START", "CONTAINS", "END").
46-
capabilities (Optional[CapabilityType]): Capabilities required for the number. (e.g., ["SMS", "VOICE"])
46+
capability (Optional[CapabilityType]): Capabilities required for the number. (e.g., ["SMS", "VOICE"])
4747
page_size (StrictInt): Maximum number of items to return.
4848
page_token (Optional[StrictStr]): Token for the next page of results.
4949
order_by (Optional[OrderByValues]): Field to order the results by. (e.g., "phoneNumber", "displayName")
@@ -54,7 +54,6 @@ def list(
5454
5555
For detailed documentation, visit https://developers.sinch.com
5656
"""
57-
5857
return TokenBasedPaginatorNumbers(
5958
sinch=self._sinch,
6059
endpoint=ListActiveNumbersEndpoint(
@@ -63,10 +62,11 @@ def list(
6362
region_code=region_code,
6463
number_type=number_type,
6564
page_size=page_size,
66-
capabilities=capabilities,
65+
capabilities=capability,
6766
number_pattern=number_pattern,
6867
number_search_pattern=number_search_pattern,
6968
page_token=page_token,
69+
order_by=order_by,
7070
**kwargs
7171
)
7272
)

sinch/domains/numbers/available_numbers.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
ActivateNumberResponse, CheckNumberAvailabilityResponse, RentAnyNumberResponse
1414
)
1515
from sinch.domains.numbers.models.numbers import (
16-
CapabilityTypeValues, Number, NumberSearchPatternTypeValues, NumberTypeValues
16+
CapabilityTypeValuesList, Number, NumberSearchPatternTypeValues, NumberTypeValues
1717
)
1818

1919

@@ -25,7 +25,7 @@ def list(
2525
number_type: NumberTypeValues,
2626
number_pattern: Optional[StrictStr] = None,
2727
number_search_pattern: Optional[NumberSearchPatternTypeValues] = None,
28-
capabilities: Optional[CapabilityTypeValues] = None,
28+
capabilities: Optional[CapabilityTypeValuesList] = None,
2929
page_size: Optional[StrictInt] = None,
3030
**kwargs
3131
) -> list[Number]:
@@ -146,7 +146,7 @@ def rent_any(
146146
sms_configuration: None,
147147
voice_configuration: None,
148148
number_pattern: Optional[NumberPatternDict] = None,
149-
capabilities: Optional[CapabilityTypeValues] = None,
149+
capabilities: Optional[CapabilityTypeValuesList] = None,
150150
callback_url: Optional[str] = None,
151151
) -> RentAnyNumberResponse:
152152
pass
@@ -159,7 +159,7 @@ def rent_any(
159159
sms_configuration: SmsConfigurationDict,
160160
voice_configuration: VoiceConfigurationDictRTC,
161161
number_pattern: Optional[NumberPatternDict] = None,
162-
capabilities: Optional[CapabilityTypeValues] = None,
162+
capabilities: Optional[CapabilityTypeValuesList] = None,
163163
callback_url: Optional[str] = None,
164164
) -> RentAnyNumberResponse:
165165
pass
@@ -172,7 +172,7 @@ def rent_any(
172172
sms_configuration: SmsConfigurationDict,
173173
voice_configuration: VoiceConfigurationDictFAX,
174174
number_pattern: Optional[NumberPatternDict] = None,
175-
capabilities: Optional[CapabilityTypeValues] = None,
175+
capabilities: Optional[CapabilityTypeValuesList] = None,
176176
callback_url: Optional[str] = None,
177177
) -> RentAnyNumberResponse:
178178
pass
@@ -185,7 +185,7 @@ def rent_any(
185185
sms_configuration: SmsConfigurationDict,
186186
voice_configuration: VoiceConfigurationDictEST,
187187
number_pattern: Optional[NumberPatternDict] = None,
188-
capabilities: Optional[CapabilityTypeValues] = None,
188+
capabilities: Optional[CapabilityTypeValuesList] = None,
189189
callback_url: Optional[str] = None,
190190
) -> RentAnyNumberResponse:
191191
pass
@@ -195,7 +195,7 @@ def rent_any(
195195
region_code: StrictStr,
196196
type_: NumberTypeValues,
197197
number_pattern: Optional[NumberPatternDict] = None,
198-
capabilities: Optional[CapabilityTypeValues] = None,
198+
capabilities: Optional[CapabilityTypeValuesList] = None,
199199
sms_configuration: Optional[SmsConfigurationDict] = None,
200200
voice_configuration: Optional[VoiceConfigurationDictType] = None,
201201
callback_url: Optional[str] = None,

sinch/domains/numbers/endpoints/available/activate_number_endpoint.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
from sinch.core.models.http_response import HTTPResponse
33
from sinch.domains.numbers.endpoints.numbers_endpoint import NumbersEndpoint
44
from sinch.domains.numbers.exceptions import NumberNotFoundException, NumbersException
5-
from sinch.domains.numbers.models.available.activate_number_request import ActivateNumberRequest
6-
from sinch.domains.numbers.models.available.activate_number_response import ActivateNumberResponse
5+
from sinch.domains.numbers.models.available import ActivateNumberRequest, ActivateNumberResponse
76

87

98
class ActivateNumberEndpoint(NumbersEndpoint):

sinch/domains/numbers/models/active/list_active_numbers_request.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
from typing import Optional
22
from pydantic import Field, StrictInt, StrictStr, field_validator
33
from sinch.domains.numbers.models.base_model_numbers import BaseModelConfigRequest
4-
from sinch.domains.numbers.models.numbers import CapabilityType, NumberType, NumberSearchPatternType, OrderByValues
4+
from sinch.domains.numbers.models.numbers import (CapabilityTypeValuesList, NumberTypeValues,
5+
NumberSearchPatternTypeValues, OrderByValues)
56

67

78
class ListActiveNumbersRequest(BaseModelConfigRequest):
89
region_code: StrictStr = Field(alias="regionCode")
9-
number_type: NumberType = Field(alias="type")
10+
number_type: NumberTypeValues = Field(alias="type")
1011
page_size: Optional[StrictInt] = Field(default=None, alias="pageSize")
11-
capabilities: Optional[CapabilityType] = Field(default=None)
12-
number_search_pattern: Optional[NumberSearchPatternType] = (
13-
Field(default=None, alias="numberPattern.searchPattern"))
12+
capabilities: Optional[CapabilityTypeValuesList] = Field(default=None)
13+
number_search_pattern: Optional[NumberSearchPatternTypeValues] = (
14+
Field(default=None, alias="numberPattern.searchPattern")
15+
)
1416
number_pattern: Optional[StrictStr] = Field(default=None, alias="numberPattern.pattern")
1517
page_token: Optional[StrictStr] = Field(default=None, alias="pageToken")
1618
order_by: Optional[OrderByValues] = Field(default=None, alias="orderBy")
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
1-
from datetime import datetime
2-
from typing import Optional
3-
from pydantic import Field, StrictInt, StrictStr
4-
from sinch.domains.numbers.models.numbers import Money, SmsConfigurationResponse, VoiceConfigurationResponse
5-
from sinch.domains.numbers.models.base_model_numbers import BaseModelConfigResponse
6-
from sinch.domains.numbers.models.numbers import CapabilityType, NumberType
1+
from sinch.domains.numbers.models.numbers import ActiveNumber
72

83

9-
class ActivateNumberResponse(BaseModelConfigResponse):
10-
phone_number: Optional[StrictStr] = Field(default=None, alias="phoneNumber")
11-
project_id: Optional[StrictStr] = Field(default=None, alias="projectId")
12-
display_name: Optional[StrictStr] = Field(default=None, alias="displayName")
13-
region_code: Optional[StrictStr] = Field(default=None, alias="regionCode")
14-
type: Optional[NumberType] = Field(default=None)
15-
capability: Optional[CapabilityType] = Field(default=None)
16-
money: Optional[Money] = Field(default=None)
17-
payment_interval_months: Optional[StrictInt] = Field(default=None, alias="paymentIntervalMonths")
18-
next_charge_date: Optional[datetime] = Field(default=None, alias="nextChargeDate")
19-
expire_at: Optional[datetime] = Field(default=None, alias="expireAt")
20-
sms_configuration: Optional[SmsConfigurationResponse] = Field(default=None, alias="smsConfiguration")
21-
voice_configuration: Optional[VoiceConfigurationResponse] = Field(default=None, alias="voiceConfiguration")
22-
callback_url: Optional[StrictStr] = Field(default=None, alias="callbackUrl")
4+
class ActivateNumberResponse(ActiveNumber):
5+
pass
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
from typing import Optional
22
from pydantic import Field, StrictInt, StrictStr
33
from sinch.domains.numbers.models.base_model_numbers import BaseModelConfigRequest
4-
from sinch.domains.numbers.models.numbers import CapabilityType, NumberType, NumberSearchPatternType
4+
from sinch.domains.numbers.models.numbers import (
5+
CapabilityTypeValuesList, NumberTypeValues, NumberSearchPatternTypeValues
6+
)
57

68

79
class ListAvailableNumbersRequest(BaseModelConfigRequest):
810
region_code: StrictStr = Field(alias="regionCode")
9-
number_type: NumberType = Field(alias="type")
11+
number_type: NumberTypeValues = Field(alias="type")
1012
page_size: Optional[StrictInt] = Field(default=None, alias="size")
11-
capabilities: Optional[CapabilityType] = Field(default=None)
12-
number_search_pattern: Optional[NumberSearchPatternType] = (
13+
capabilities: Optional[CapabilityTypeValuesList] = Field(default=None)
14+
number_search_pattern: Optional[NumberSearchPatternTypeValues] = (
1315
Field(default=None, alias="numberPattern.searchPattern"))
1416
number_pattern: Optional[StrictStr] = Field(default=None, alias="numberPattern.pattern")

sinch/domains/numbers/models/available/rent_any_number_response.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
from typing import Optional
33
from pydantic import Field, StrictStr, StrictInt
44
from sinch.domains.numbers.models.base_model_numbers import BaseModelConfigResponse
5-
from sinch.domains.numbers.models.numbers import (CapabilityType, Money, NumberType,
5+
from sinch.domains.numbers.models.numbers import (CapabilityTypeValuesList, Money, NumberTypeValues,
66
SmsConfigurationResponse, VoiceConfigurationResponse)
77

88

99
class RentAnyNumberResponse(BaseModelConfigResponse):
1010
phone_number: Optional[StrictStr] = Field(default=None, alias="phoneNumber")
1111
project_id: Optional[StrictStr] = Field(default=None, alias="projectId")
1212
region_code: Optional[StrictStr] = Field(default=None, alias="regionCode")
13-
type: Optional[NumberType] = Field(default=None)
14-
capability: Optional[CapabilityType] = Field(default=None)
13+
type: Optional[NumberTypeValues] = Field(default=None)
14+
capability: Optional[CapabilityTypeValuesList] = Field(default=None)
1515
money: Optional[Money] = Field(default=None)
1616
payment_interval_months: Optional[StrictInt] = Field(default=None, alias="paymentIntervalMonths")
1717
next_charge_date: Optional[datetime] = Field(default=None, alias="nextChargeDate")

sinch/domains/numbers/models/numbers.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
from sinch.domains.numbers.models.base_model_numbers import BaseModelConfigRequest, BaseModelConfigResponse
66

77
NumberTypeValues = Union[Literal["MOBILE", "LOCAL", "TOLL_FREE"], StrictStr]
8-
CapabilityTypeValues = conlist(Union[Literal["SMS", "VOICE"], StrictStr], min_length=1)
8+
CapabilityTypeValuesList = conlist(Union[Literal["SMS", "VOICE"], StrictStr], min_length=1)
99
NumberSearchPatternTypeValues = Union[Literal["START", "CONTAINS", "END"], StrictStr]
1010
OrderByValues = Union[Literal["phoneNumber", "displayName"], StrictStr]
1111

1212
CapabilityType = Annotated[
13-
CapabilityTypeValues,
13+
CapabilityTypeValuesList,
1414
Field(default=None)
1515
]
1616

@@ -128,6 +128,7 @@ class ActiveNumber(BaseModelConfigResponse):
128128
expire_at: Optional[datetime] = Field(default=None, alias="expireAt")
129129
sms_configuration: Optional[SmsConfigurationResponse] = Field(default=None, alias="smsConfiguration")
130130
voice_configuration: Optional[VoiceConfigurationResponse] = Field(default=None, alias="voiceConfiguration")
131+
callback_url: Optional[StrictStr] = Field(default=None, alias="callbackUrl")
131132

132133

133134
class Number(BaseModelConfigResponse):

tests/unit/domains/numbers/endpoints/active/test_list_active_numbers_endpoint.py

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from datetime import datetime, timezone
2+
from decimal import Decimal
3+
14
import pytest
25
from sinch.domains.numbers.endpoints.active.list_active_numbers_endpoint import ListActiveNumbersEndpoint
36
from sinch.domains.numbers.models.active.list_active_numbers_request import ListActiveNumbersRequest
@@ -10,7 +13,7 @@ def request_data():
1013
region_code="AR",
1114
number_type="LOCAL",
1215
page_size=15,
13-
capabilities=["SMS"],
16+
capabilities=["SMS", "VOICE"],
1417
number_pattern="123",
1518
number_search_pattern="STARTS_WITH"
1619
)
@@ -35,7 +38,9 @@ def mock_response():
3538
"paymentIntervalMonths": 1,
3639
"nextChargeDate": "2025-02-28T14:04:26.190127Z",
3740
"expireAt": "2025-02-28T14:04:26.190127Z",
38-
"callbackUrl": "https://yourcallback/numbers"}],
41+
"callbackUrl": "https://yourcallback/numbers"
42+
}
43+
],
3944
"nextPageToken": "CgtwaG9uoLnNDQzajQSDCsxMzE1OTA0MzM1OQ==",
4045
"totalSize": 10
4146
},
@@ -57,7 +62,7 @@ def test_build_query_params_expects_correct_mapping(endpoint):
5762
"regionCode": "AR",
5863
"type": "LOCAL",
5964
"pageSize": 15,
60-
"capabilities": ["SMS"],
65+
"capabilities": ["SMS", "VOICE"],
6166
"numberPattern.pattern": "123",
6267
"numberPattern.searchPattern": "STARTS_WITH"
6368
}
@@ -72,3 +77,18 @@ def test_handle_response_expects_correct_mapping(endpoint, mock_response):
7277
assert parsed_response.active_numbers[0].phone_number == "+1234567890"
7378
assert parsed_response.active_numbers[0].project_id == "37b62a7b-0177-429a-bb0b-e10f848de0b8"
7479
assert parsed_response.active_numbers[0].display_name == ""
80+
assert parsed_response.active_numbers[0].region_code == "US"
81+
assert parsed_response.active_numbers[0].type == "LOCAL"
82+
assert parsed_response.active_numbers[0].capability == ["SMS", "VOICE"]
83+
assert parsed_response.active_numbers[0].money.currency_code == "EUR"
84+
assert parsed_response.active_numbers[0].money.amount == Decimal("0.80")
85+
assert parsed_response.active_numbers[0].payment_interval_months == 1
86+
expected_next_charge_date = (
87+
datetime(2025, 2, 28, 14, 4, 26, 190127, tzinfo=timezone.utc))
88+
assert parsed_response.active_numbers[0].next_charge_date == expected_next_charge_date
89+
expected_expire_at = (
90+
datetime(2025, 2, 28, 14, 4, 26, 190127, tzinfo=timezone.utc))
91+
assert parsed_response.active_numbers[0].expire_at == expected_expire_at
92+
assert parsed_response.active_numbers[0].callback_url == "https://yourcallback/numbers"
93+
assert parsed_response.next_page_token == "CgtwaG9uoLnNDQzajQSDCsxMzE1OTA0MzM1OQ=="
94+
assert parsed_response.total_size == 10

tests/unit/test_pagination.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,13 @@ async def test_page_int_iterator_async_using_auto_pagination(
130130
page_counter = 0
131131
assert int_based_paginator.result.page == page_counter
132132

133-
page_counter = 0
133+
# Previous implementation starts from second page
134+
page_counter = 1
134135
async for page in int_based_paginator.auto_paging_iter():
135136
page_counter += 1
136137
assert isinstance(page, AsyncIntBasedPaginator)
137138

138-
assert page_counter == 2
139+
assert page_counter == 3
139140
assert not int_based_paginator.result.pig_dogs
140141

141142

@@ -188,12 +189,12 @@ def test_page_token_iterator_sync_using_auto_pagination(
188189
)
189190
assert token_based_paginator
190191

191-
page_counter = 0
192+
page_counter = 1
192193
for page in token_based_paginator.auto_paging_iter():
193194
page_counter += 1
194195
assert isinstance(page, TokenBasedPaginator)
195196

196-
assert page_counter == 1
197+
assert page_counter == 2
197198

198199
def test_page_token_iterator_numbers_sync_using_auto_pagination_expects_iter(int_based_pagination_request_data):
199200
""" Test that the pagination iterates correctly through multiple pages. """
@@ -264,13 +265,13 @@ async def test_page_token_iterator_async_using_manual_pagination(
264265
)
265266
assert token_based_paginator
266267

267-
page_counter = 0
268+
page_counter = 1
268269
while token_based_paginator.has_next_page:
269270
token_based_paginator = await token_based_paginator.next_page()
270271
page_counter += 1
271272
assert isinstance(token_based_paginator, AsyncTokenBasedPaginator)
272273

273-
assert page_counter == 2
274+
assert page_counter == 3
274275

275276

276277
async def test_page_token_iterator_async_using_auto_pagination(
@@ -294,9 +295,9 @@ async def test_page_token_iterator_async_using_auto_pagination(
294295
)
295296
assert token_based_paginator
296297

297-
page_counter = 0
298+
page_counter = 1
298299
async for page in token_based_paginator.auto_paging_iter():
299300
page_counter += 1
300301
assert isinstance(page, AsyncTokenBasedPaginator)
301302

302-
assert page_counter == 2
303+
assert page_counter == 3

0 commit comments

Comments
 (0)