Skip to content

Commit

Permalink
refactor: refactored models, updated unit test, and added parameter i…
Browse files Browse the repository at this point in the history
…n API call
  • Loading branch information
matsk-sinch committed Feb 17, 2025
1 parent 210f58e commit 40521ae
Show file tree
Hide file tree
Showing 11 changed files with 70 additions and 70 deletions.
14 changes: 3 additions & 11 deletions sinch/core/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ async def _initialize(cls, sinch, endpoint):
return cls(sinch, endpoint, result)


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

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

def __repr__(self):
pass

def _calculate_next_page(self):
self.has_next_page = bool(self.result.next_page_token)

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


class TokenBasedPaginator(TokenBasedPaginatorBase):
"""Paginator that skips the first page."""
pass


class TokenBasedPaginatorNumbers(TokenBasedPaginatorBase):
class TokenBasedPaginatorNumbers(TokenBasedPaginator):
"""
Paginator for handling token-based pagination specifically for phone numbers.
Expand Down Expand Up @@ -231,7 +223,7 @@ def wrapper():
return wrapper


class AsyncTokenBasedPaginator(TokenBasedPaginatorBase):
class AsyncTokenBasedPaginator(TokenBasedPaginator):
"""Asynchronous token-based paginator."""

async def next_page(self):
Expand Down
10 changes: 5 additions & 5 deletions sinch/domains/numbers/active_numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
UpdateNumberConfigurationResponse, GetNumberConfigurationResponse, ReleaseNumberFromProjectResponse
)
from sinch.domains.numbers.models.numbers import (
NumberTypeValues, CapabilityTypeValues, NumberSearchPatternTypeValues, OrderByValues
CapabilityTypeValuesList, NumberTypeValues, NumberSearchPatternTypeValues, OrderByValues
)


Expand All @@ -28,7 +28,7 @@ def list(
number_type: NumberTypeValues,
number_pattern: Optional[StrictStr] = None,
number_search_pattern: Optional[NumberSearchPatternTypeValues] = None,
capabilities: Optional[CapabilityTypeValues] = None,
capability: Optional[CapabilityTypeValuesList] = None,
page_size: Optional[StrictInt] = None,
page_token: Optional[StrictStr] = None,
order_by: Optional[OrderByValues] = None,
Expand All @@ -43,7 +43,7 @@ def list(
number_pattern (Optional[StrictStr]): Specific sequence of digits to search for.
number_search_pattern (Optional[NumberSearchPatternType]):
Pattern to apply (e.g., "START", "CONTAINS", "END").
capabilities (Optional[CapabilityType]): Capabilities required for the number. (e.g., ["SMS", "VOICE"])
capability (Optional[CapabilityType]): Capabilities required for the number. (e.g., ["SMS", "VOICE"])
page_size (StrictInt): Maximum number of items to return.
page_token (Optional[StrictStr]): Token for the next page of results.
order_by (Optional[OrderByValues]): Field to order the results by. (e.g., "phoneNumber", "displayName")
Expand All @@ -54,7 +54,6 @@ def list(
For detailed documentation, visit https://developers.sinch.com
"""

return TokenBasedPaginatorNumbers(
sinch=self._sinch,
endpoint=ListActiveNumbersEndpoint(
Expand All @@ -63,10 +62,11 @@ def list(
region_code=region_code,
number_type=number_type,
page_size=page_size,
capabilities=capabilities,
capabilities=capability,
number_pattern=number_pattern,
number_search_pattern=number_search_pattern,
page_token=page_token,
order_by=order_by,
**kwargs
)
)
Expand Down
14 changes: 7 additions & 7 deletions sinch/domains/numbers/available_numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
ActivateNumberResponse, CheckNumberAvailabilityResponse, RentAnyNumberResponse
)
from sinch.domains.numbers.models.numbers import (
CapabilityTypeValues, Number, NumberSearchPatternTypeValues, NumberTypeValues
CapabilityTypeValuesList, Number, NumberSearchPatternTypeValues, NumberTypeValues
)


Expand All @@ -25,7 +25,7 @@ def list(
number_type: NumberTypeValues,
number_pattern: Optional[StrictStr] = None,
number_search_pattern: Optional[NumberSearchPatternTypeValues] = None,
capabilities: Optional[CapabilityTypeValues] = None,
capabilities: Optional[CapabilityTypeValuesList] = None,
page_size: Optional[StrictInt] = None,
**kwargs
) -> list[Number]:
Expand Down Expand Up @@ -146,7 +146,7 @@ def rent_any(
sms_configuration: None,
voice_configuration: None,
number_pattern: Optional[NumberPatternDict] = None,
capabilities: Optional[CapabilityTypeValues] = None,
capabilities: Optional[CapabilityTypeValuesList] = None,
callback_url: Optional[str] = None,
) -> RentAnyNumberResponse:
pass
Expand All @@ -159,7 +159,7 @@ def rent_any(
sms_configuration: SmsConfigurationDict,
voice_configuration: VoiceConfigurationDictRTC,
number_pattern: Optional[NumberPatternDict] = None,
capabilities: Optional[CapabilityTypeValues] = None,
capabilities: Optional[CapabilityTypeValuesList] = None,
callback_url: Optional[str] = None,
) -> RentAnyNumberResponse:
pass
Expand All @@ -172,7 +172,7 @@ def rent_any(
sms_configuration: SmsConfigurationDict,
voice_configuration: VoiceConfigurationDictFAX,
number_pattern: Optional[NumberPatternDict] = None,
capabilities: Optional[CapabilityTypeValues] = None,
capabilities: Optional[CapabilityTypeValuesList] = None,
callback_url: Optional[str] = None,
) -> RentAnyNumberResponse:
pass
Expand All @@ -185,7 +185,7 @@ def rent_any(
sms_configuration: SmsConfigurationDict,
voice_configuration: VoiceConfigurationDictEST,
number_pattern: Optional[NumberPatternDict] = None,
capabilities: Optional[CapabilityTypeValues] = None,
capabilities: Optional[CapabilityTypeValuesList] = None,
callback_url: Optional[str] = None,
) -> RentAnyNumberResponse:
pass
Expand All @@ -195,7 +195,7 @@ def rent_any(
region_code: StrictStr,
type_: NumberTypeValues,
number_pattern: Optional[NumberPatternDict] = None,
capabilities: Optional[CapabilityTypeValues] = None,
capabilities: Optional[CapabilityTypeValuesList] = None,
sms_configuration: Optional[SmsConfigurationDict] = None,
voice_configuration: Optional[VoiceConfigurationDictType] = None,
callback_url: Optional[str] = None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
from sinch.core.models.http_response import HTTPResponse
from sinch.domains.numbers.endpoints.numbers_endpoint import NumbersEndpoint
from sinch.domains.numbers.exceptions import NumberNotFoundException, NumbersException
from sinch.domains.numbers.models.available.activate_number_request import ActivateNumberRequest
from sinch.domains.numbers.models.available.activate_number_response import ActivateNumberResponse
from sinch.domains.numbers.models.available import ActivateNumberRequest, ActivateNumberResponse


class ActivateNumberEndpoint(NumbersEndpoint):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from typing import Optional
from pydantic import Field, StrictInt, StrictStr, field_validator
from sinch.domains.numbers.models.base_model_numbers import BaseModelConfigRequest
from sinch.domains.numbers.models.numbers import CapabilityType, NumberType, NumberSearchPatternType, OrderByValues
from sinch.domains.numbers.models.numbers import (CapabilityTypeValuesList, NumberTypeValues,
NumberSearchPatternTypeValues, OrderByValues)


class ListActiveNumbersRequest(BaseModelConfigRequest):
region_code: StrictStr = Field(alias="regionCode")
number_type: NumberType = Field(alias="type")
number_type: NumberTypeValues = Field(alias="type")
page_size: Optional[StrictInt] = Field(default=None, alias="pageSize")
capabilities: Optional[CapabilityType] = Field(default=None)
number_search_pattern: Optional[NumberSearchPatternType] = (
Field(default=None, alias="numberPattern.searchPattern"))
capabilities: Optional[CapabilityTypeValuesList] = Field(default=None)
number_search_pattern: Optional[NumberSearchPatternTypeValues] = (
Field(default=None, alias="numberPattern.searchPattern")
)
number_pattern: Optional[StrictStr] = Field(default=None, alias="numberPattern.pattern")
page_token: Optional[StrictStr] = Field(default=None, alias="pageToken")
order_by: Optional[OrderByValues] = Field(default=None, alias="orderBy")
Expand Down
23 changes: 3 additions & 20 deletions sinch/domains/numbers/models/available/activate_number_response.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,5 @@
from datetime import datetime
from typing import Optional
from pydantic import Field, StrictInt, StrictStr
from sinch.domains.numbers.models.numbers import Money, SmsConfigurationResponse, VoiceConfigurationResponse
from sinch.domains.numbers.models.base_model_numbers import BaseModelConfigResponse
from sinch.domains.numbers.models.numbers import CapabilityType, NumberType
from sinch.domains.numbers.models.numbers import ActiveNumber


class ActivateNumberResponse(BaseModelConfigResponse):
phone_number: Optional[StrictStr] = Field(default=None, alias="phoneNumber")
project_id: Optional[StrictStr] = Field(default=None, alias="projectId")
display_name: Optional[StrictStr] = Field(default=None, alias="displayName")
region_code: Optional[StrictStr] = Field(default=None, alias="regionCode")
type: Optional[NumberType] = Field(default=None)
capability: Optional[CapabilityType] = Field(default=None)
money: Optional[Money] = Field(default=None)
payment_interval_months: Optional[StrictInt] = Field(default=None, alias="paymentIntervalMonths")
next_charge_date: Optional[datetime] = Field(default=None, alias="nextChargeDate")
expire_at: Optional[datetime] = Field(default=None, alias="expireAt")
sms_configuration: Optional[SmsConfigurationResponse] = Field(default=None, alias="smsConfiguration")
voice_configuration: Optional[VoiceConfigurationResponse] = Field(default=None, alias="voiceConfiguration")
callback_url: Optional[StrictStr] = Field(default=None, alias="callbackUrl")
class ActivateNumberResponse(ActiveNumber):
pass
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from typing import Optional
from pydantic import Field, StrictInt, StrictStr
from sinch.domains.numbers.models.base_model_numbers import BaseModelConfigRequest
from sinch.domains.numbers.models.numbers import CapabilityType, NumberType, NumberSearchPatternType
from sinch.domains.numbers.models.numbers import (
CapabilityTypeValuesList, NumberTypeValues, NumberSearchPatternTypeValues
)


class ListAvailableNumbersRequest(BaseModelConfigRequest):
region_code: StrictStr = Field(alias="regionCode")
number_type: NumberType = Field(alias="type")
number_type: NumberTypeValues = Field(alias="type")
page_size: Optional[StrictInt] = Field(default=None, alias="size")
capabilities: Optional[CapabilityType] = Field(default=None)
number_search_pattern: Optional[NumberSearchPatternType] = (
capabilities: Optional[CapabilityTypeValuesList] = Field(default=None)
number_search_pattern: Optional[NumberSearchPatternTypeValues] = (
Field(default=None, alias="numberPattern.searchPattern"))
number_pattern: Optional[StrictStr] = Field(default=None, alias="numberPattern.pattern")
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
from typing import Optional
from pydantic import Field, StrictStr, StrictInt
from sinch.domains.numbers.models.base_model_numbers import BaseModelConfigResponse
from sinch.domains.numbers.models.numbers import (CapabilityType, Money, NumberType,
from sinch.domains.numbers.models.numbers import (CapabilityTypeValuesList, Money, NumberTypeValues,
SmsConfigurationResponse, VoiceConfigurationResponse)


class RentAnyNumberResponse(BaseModelConfigResponse):
phone_number: Optional[StrictStr] = Field(default=None, alias="phoneNumber")
project_id: Optional[StrictStr] = Field(default=None, alias="projectId")
region_code: Optional[StrictStr] = Field(default=None, alias="regionCode")
type: Optional[NumberType] = Field(default=None)
capability: Optional[CapabilityType] = Field(default=None)
type: Optional[NumberTypeValues] = Field(default=None)
capability: Optional[CapabilityTypeValuesList] = Field(default=None)
money: Optional[Money] = Field(default=None)
payment_interval_months: Optional[StrictInt] = Field(default=None, alias="paymentIntervalMonths")
next_charge_date: Optional[datetime] = Field(default=None, alias="nextChargeDate")
Expand Down
5 changes: 3 additions & 2 deletions sinch/domains/numbers/models/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
from sinch.domains.numbers.models.base_model_numbers import BaseModelConfigRequest, BaseModelConfigResponse

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

CapabilityType = Annotated[
CapabilityTypeValues,
CapabilityTypeValuesList,
Field(default=None)
]

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


class Number(BaseModelConfigResponse):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from datetime import datetime, timezone
from decimal import Decimal

import pytest
from sinch.domains.numbers.endpoints.active.list_active_numbers_endpoint import ListActiveNumbersEndpoint
from sinch.domains.numbers.models.active.list_active_numbers_request import ListActiveNumbersRequest
Expand All @@ -10,7 +13,7 @@ def request_data():
region_code="AR",
number_type="LOCAL",
page_size=15,
capabilities=["SMS"],
capabilities=["SMS", "VOICE"],
number_pattern="123",
number_search_pattern="STARTS_WITH"
)
Expand All @@ -35,7 +38,9 @@ def mock_response():
"paymentIntervalMonths": 1,
"nextChargeDate": "2025-02-28T14:04:26.190127Z",
"expireAt": "2025-02-28T14:04:26.190127Z",
"callbackUrl": "https://yourcallback/numbers"}],
"callbackUrl": "https://yourcallback/numbers"
}
],
"nextPageToken": "CgtwaG9uoLnNDQzajQSDCsxMzE1OTA0MzM1OQ==",
"totalSize": 10
},
Expand All @@ -57,7 +62,7 @@ def test_build_query_params_expects_correct_mapping(endpoint):
"regionCode": "AR",
"type": "LOCAL",
"pageSize": 15,
"capabilities": ["SMS"],
"capabilities": ["SMS", "VOICE"],
"numberPattern.pattern": "123",
"numberPattern.searchPattern": "STARTS_WITH"
}
Expand All @@ -72,3 +77,18 @@ def test_handle_response_expects_correct_mapping(endpoint, mock_response):
assert parsed_response.active_numbers[0].phone_number == "+1234567890"
assert parsed_response.active_numbers[0].project_id == "37b62a7b-0177-429a-bb0b-e10f848de0b8"
assert parsed_response.active_numbers[0].display_name == ""
assert parsed_response.active_numbers[0].region_code == "US"
assert parsed_response.active_numbers[0].type == "LOCAL"
assert parsed_response.active_numbers[0].capability == ["SMS", "VOICE"]
assert parsed_response.active_numbers[0].money.currency_code == "EUR"
assert parsed_response.active_numbers[0].money.amount == Decimal("0.80")
assert parsed_response.active_numbers[0].payment_interval_months == 1
expected_next_charge_date = (
datetime(2025, 2, 28, 14, 4, 26, 190127, tzinfo=timezone.utc))
assert parsed_response.active_numbers[0].next_charge_date == expected_next_charge_date
expected_expire_at = (
datetime(2025, 2, 28, 14, 4, 26, 190127, tzinfo=timezone.utc))
assert parsed_response.active_numbers[0].expire_at == expected_expire_at
assert parsed_response.active_numbers[0].callback_url == "https://yourcallback/numbers"
assert parsed_response.next_page_token == "CgtwaG9uoLnNDQzajQSDCsxMzE1OTA0MzM1OQ=="
assert parsed_response.total_size == 10
17 changes: 9 additions & 8 deletions tests/unit/test_pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,13 @@ async def test_page_int_iterator_async_using_auto_pagination(
page_counter = 0
assert int_based_paginator.result.page == page_counter

page_counter = 0
# Previous implementation starts from second page
page_counter = 1
async for page in int_based_paginator.auto_paging_iter():
page_counter += 1
assert isinstance(page, AsyncIntBasedPaginator)

assert page_counter == 2
assert page_counter == 3
assert not int_based_paginator.result.pig_dogs


Expand Down Expand Up @@ -188,12 +189,12 @@ def test_page_token_iterator_sync_using_auto_pagination(
)
assert token_based_paginator

page_counter = 0
page_counter = 1
for page in token_based_paginator.auto_paging_iter():
page_counter += 1
assert isinstance(page, TokenBasedPaginator)

assert page_counter == 1
assert page_counter == 2

def test_page_token_iterator_numbers_sync_using_auto_pagination_expects_iter(int_based_pagination_request_data):
""" Test that the pagination iterates correctly through multiple pages. """
Expand Down Expand Up @@ -264,13 +265,13 @@ async def test_page_token_iterator_async_using_manual_pagination(
)
assert token_based_paginator

page_counter = 0
page_counter = 1
while token_based_paginator.has_next_page:
token_based_paginator = await token_based_paginator.next_page()
page_counter += 1
assert isinstance(token_based_paginator, AsyncTokenBasedPaginator)

assert page_counter == 2
assert page_counter == 3


async def test_page_token_iterator_async_using_auto_pagination(
Expand All @@ -294,9 +295,9 @@ async def test_page_token_iterator_async_using_auto_pagination(
)
assert token_based_paginator

page_counter = 0
page_counter = 1
async for page in token_based_paginator.auto_paging_iter():
page_counter += 1
assert isinstance(page, AsyncTokenBasedPaginator)

assert page_counter == 2
assert page_counter == 3

0 comments on commit 40521ae

Please sign in to comment.