diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index ccb9d2ea..1733a2f4 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -190,3 +190,96 @@ Note that `sinch.sms.groups` and `sinch.sms.inbounds` are not supported yet and | `list()` with `ListSMSDeliveryReportsRequest` | `list()` the parameters `start_date` and `end_date` now accepts both `str` and `datetime` | | `get_for_batch()` with `GetSMSDeliveryReportForBatchRequest` | `get()` with `batch_id: str` and optional parameters: `report_type`, `status`, `code`, `client_reference` | | `get_for_number()` with `GetSMSDeliveryReportForNumberRequest` | `get_for_number()` with `batch_id: str` and `recipient: str` parameters | + +
+ +### [`Numbers` (Virtual Numbers)](https://github.com/sinch/sinch-sdk-python/tree/main/sinch/domains/numbers) + +##### Replacement APIs / attributes + +| Old | New | +|-----|-----| +| `sinch_client.numbers.callbacks` (attribute) | `sinch_client.numbers.event_destinations` (attribute) | +| `numbers.callbacks.get_configuration()` (method) | `numbers.event_destinations.get()` (method) | +| `numbers.callbacks.update_configuration(hmac_secret)` (method) | `numbers.event_destinations.update(hmac_secret=hmac_secret)` (method) | + +##### Replacement models + +| Old class | New class | +|-----------|-----------| +| `UpdateNumbersCallbackConfigurationRequest` | `UpdateEventDestinationRequest` | +| `GetNumbersCallbackConfigurationResponse` | `EventDestinationResponse` | +| `UpdateNumbersCallbackConfigurationResponse` | `EventDestinationResponse` | + +**Example:** + +```python +# Old +config = sinch_client.numbers.callbacks.get_configuration() +sinch_client.numbers.callbacks.update_configuration("your_hmac_secret") + +# New +config = sinch_client.numbers.event_destinations.get() +sinch_client.numbers.event_destinations.update(hmac_secret="your_hmac_secret") +``` + +##### Available and Active: method locations + +| Old method | New method | +|------------|------------| +| `numbers.available.rent_any(...)`, `numbers.available.activate(...)`, `numbers.available.check_availability(...)`, `numbers.available.list(...)` | `numbers.rent_any(...)`, `numbers.rent(...)`, `numbers.check_availability(...)`, `numbers.search_for_available_numbers(...)` | +| `numbers.active.list(...)`, `numbers.active.get(...)`, `numbers.active.update(...)`, `numbers.active.release(...)` | `numbers.list(...)`, `numbers.get(...)`, `numbers.update(...)`, `numbers.release(...)` | + +#### Sinch Events (Event Destinations payload models and package path) + +| Old | New | +|-----|-----| +| — _(N/A)_ | `sinch.domains.numbers.sinch_events` (package path) | +| — | `NumberSinchEvent` (class, payload model) | + +To obtain a Numbers Sinch Events handler: `sinch_client.numbers.sinch_events(callback_secret)` returns a `SinchEvents` instance; `handler.parse_event(request_body)` returns a `NumberSinchEvent`. + + +```python +# New +from sinch.domains.numbers.sinch_events.v1.events import NumberSinchEvent +handler = sinch_client.numbers.sinch_events("your_callback_secret") +event = handler.parse_event(request_body) # event is a NumberSinchEvent +``` + +#### Request and response fields: callback URL → event destination target + +| | Old | New | +|---|-----|-----| +| **Methods that accept the parameter** | Only `numbers.available.rent_any(..., callback_url=...)` | `numbers.rent(...)`, `numbers.rent_any(...)`, and `numbers.update(...)` accept `event_destination_target` | +| **Parameter name** | `callback_url` | `event_destination_target` | + + +##### Replacement request/response attributes + +| Old | New | +|-----|-----| +| `RentAnyNumberRequest.callback_url` | `RentNumberRequest.event_destination_target`, `RentAnyNumberRequest.event_destination_target`, `UpdateNumberConfigurationRequest.event_destination_target` | +| `ActiveNumber` has no callback field | `ActiveNumber.event_destination_target` (response) | + +**Example:** + +```python +# Old +sinch_client.numbers.available.rent_any( + region_code="US", + type_="LOCAL", + sms_configuration={...}, + voice_configuration={...}, + callback_url="https://example.com/events", +) + +# New +sinch_client.numbers.rent_any( + region_code="US", + number_type="LOCAL", + sms_configuration={...}, + voice_configuration={...}, + event_destination_target="https://example.com/events", +) +``` diff --git a/examples/webhooks/.env.example b/examples/sinch_events/.env.example similarity index 100% rename from examples/webhooks/.env.example rename to examples/sinch_events/.env.example diff --git a/examples/webhooks/README.md b/examples/sinch_events/README.md similarity index 87% rename from examples/webhooks/README.md rename to examples/sinch_events/README.md index c2f88a45..20693812 100644 --- a/examples/webhooks/README.md +++ b/examples/sinch_events/README.md @@ -1,4 +1,4 @@ -# Webhook Handlers for Sinch Python SDK +# Sinch Events Handlers for Sinch Python SDK This directory contains a server application built with [Sinch Python SDK](https://github.com/sinch/sinch-sdk-python) to process incoming webhooks from Sinch services. @@ -21,7 +21,7 @@ This directory contains both the webhook handlers and the server application (`s ## Configuration 1. **Environment Variables**: - Rename [.env.example](.env.example) to `.env` in this directory (`examples/webhooks/`), then add your credentials from the Sinch dashboard under the Access Keys section. + Rename [.env.example](.env.example) to `.env` in this directory (`examples/sinch_events/`), then add your credentials from the Sinch dashboard under the Access Keys section. - Server Port: Define the port your server will listen to on (default: 3001): @@ -30,7 +30,7 @@ This directory contains both the webhook handlers and the server application (`s ``` - Controller Settings - - Numbers controller: Set the `numbers` webhook secret. You can retrieve it using the `/callback_configuration` endpoint (see SDK implementation: [callback_configuration_apis.py](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/numbers/api/v1/callback_configuration_apis.py); for additional details, refer to the [Numbers API callbacks documentation](https://developers.sinch.com/docs/numbers/api-reference/numbers/tag/Numbers-Callbacks/)): + - Numbers controller: Set the `numbers` Sinch Event secret. You can retrieve it using the `/event_destination` endpoint (see SDK implementation: [event_destinations_apis.py](https://github.com/sinch/sinch-sdk-python/blob/v2.0/sinch/domains/numbers/api/v1/event_destinations_apis.py); for additional details, refer to the [Numbers API callbacks documentation](https://developers.sinch.com/docs/numbers/api-reference/numbers/tag/Numbers-Callbacks/)): ``` NUMBERS_WEBHOOKS_SECRET=Your Sinch Numbers Webhook Secret ``` @@ -49,9 +49,9 @@ This directory contains both the webhook handlers and the server application (`s ### Running the server application -1. Navigate to the webhooks' directory: +1. Navigate to the examples events directory: ``` - cd examples/webhooks + cd examples/sinch_events ``` 2. Install the project dependencies: diff --git a/examples/webhooks/conversation_api/__init__.py b/examples/sinch_events/conversation_api/__init__.py similarity index 100% rename from examples/webhooks/conversation_api/__init__.py rename to examples/sinch_events/conversation_api/__init__.py diff --git a/examples/webhooks/conversation_api/controller.py b/examples/sinch_events/conversation_api/controller.py similarity index 100% rename from examples/webhooks/conversation_api/controller.py rename to examples/sinch_events/conversation_api/controller.py diff --git a/examples/webhooks/conversation_api/server_business_logic.py b/examples/sinch_events/conversation_api/server_business_logic.py similarity index 100% rename from examples/webhooks/conversation_api/server_business_logic.py rename to examples/sinch_events/conversation_api/server_business_logic.py diff --git a/examples/webhooks/numbers_api/__init__.py b/examples/sinch_events/numbers_api/__init__.py similarity index 100% rename from examples/webhooks/numbers_api/__init__.py rename to examples/sinch_events/numbers_api/__init__.py diff --git a/examples/webhooks/numbers_api/controller.py b/examples/sinch_events/numbers_api/controller.py similarity index 76% rename from examples/webhooks/numbers_api/controller.py rename to examples/sinch_events/numbers_api/controller.py index 2380eda4..00eaac47 100644 --- a/examples/webhooks/numbers_api/controller.py +++ b/examples/sinch_events/numbers_api/controller.py @@ -12,11 +12,13 @@ def numbers_event(self): headers = dict(request.headers) raw_body = request.raw_body if request.raw_body else b"" - webhooks_service = self.sinch_client.numbers.webhooks(self.webhooks_secret) + sinch_events_service = self.sinch_client.numbers.sinch_events( + self.webhooks_secret + ) ensure_valid_authentication = False if ensure_valid_authentication: - valid_auth = webhooks_service.validate_authentication_header( + valid_auth = sinch_events_service.validate_authentication_header( headers=headers, json_payload=raw_body, ) @@ -24,7 +26,7 @@ def numbers_event(self): if not valid_auth: return Response(status=401) - event = webhooks_service.parse_event(raw_body, headers) + event = sinch_events_service.parse_event(raw_body, headers) handle_numbers_event(numbers_event=event, logger=self.logger) diff --git a/examples/webhooks/numbers_api/server_business_logic.py b/examples/sinch_events/numbers_api/server_business_logic.py similarity index 50% rename from examples/webhooks/numbers_api/server_business_logic.py rename to examples/sinch_events/numbers_api/server_business_logic.py index 80082812..305772ce 100644 --- a/examples/webhooks/numbers_api/server_business_logic.py +++ b/examples/sinch_events/numbers_api/server_business_logic.py @@ -1,11 +1,11 @@ -from sinch.domains.numbers.webhooks.v1.events.numbers_webhooks_event import NumbersWebhooksEvent +from sinch.domains.numbers.sinch_events.v1.events import NumberSinchEvent -def handle_numbers_event(numbers_event: NumbersWebhooksEvent, logger): +def handle_numbers_event(numbers_event: NumberSinchEvent, logger): """ This method handles a Numbers event. Args: - numbers_event (NumbersWebhooksEvent): The Numbers event data. + numbers_event (NumberSinchEvent): The Numbers event data. logger (logging.Logger, optional): Logger instance for logging. Defaults to None. """ logger.info(f'Handling Numbers event:\n{numbers_event.model_dump_json(indent=2)}') diff --git a/examples/webhooks/pyproject.toml b/examples/sinch_events/pyproject.toml similarity index 100% rename from examples/webhooks/pyproject.toml rename to examples/sinch_events/pyproject.toml diff --git a/examples/webhooks/server.py b/examples/sinch_events/server.py similarity index 100% rename from examples/webhooks/server.py rename to examples/sinch_events/server.py diff --git a/examples/webhooks/sinch_client_helper.py b/examples/sinch_events/sinch_client_helper.py similarity index 100% rename from examples/webhooks/sinch_client_helper.py rename to examples/sinch_events/sinch_client_helper.py diff --git a/examples/webhooks/sms_api/__init__.py b/examples/sinch_events/sms_api/__init__.py similarity index 100% rename from examples/webhooks/sms_api/__init__.py rename to examples/sinch_events/sms_api/__init__.py diff --git a/examples/webhooks/sms_api/controller.py b/examples/sinch_events/sms_api/controller.py similarity index 100% rename from examples/webhooks/sms_api/controller.py rename to examples/sinch_events/sms_api/controller.py diff --git a/examples/webhooks/sms_api/server_business_logic.py b/examples/sinch_events/sms_api/server_business_logic.py similarity index 100% rename from examples/webhooks/sms_api/server_business_logic.py rename to examples/sinch_events/sms_api/server_business_logic.py diff --git a/examples/snippets/numbers/callback_configuration/get/snippet.py b/examples/snippets/numbers/event_destinations/get/snippet.py similarity index 81% rename from examples/snippets/numbers/callback_configuration/get/snippet.py rename to examples/snippets/numbers/event_destinations/get/snippet.py index e1593f73..413fd0d0 100644 --- a/examples/snippets/numbers/callback_configuration/get/snippet.py +++ b/examples/snippets/numbers/event_destinations/get/snippet.py @@ -17,6 +17,6 @@ key_secret=os.environ.get("SINCH_KEY_SECRET") or "MY_KEY_SECRET" ) -response = sinch_client.numbers.callback_configuration.get() +response = sinch_client.numbers.event_destinations.get() -print("Callback Configuration:\n", response) +print("Event Destination Configuration:\n", response) diff --git a/examples/snippets/numbers/callback_configuration/update/snippet.py b/examples/snippets/numbers/event_destinations/update/snippet.py similarity index 81% rename from examples/snippets/numbers/callback_configuration/update/snippet.py rename to examples/snippets/numbers/event_destinations/update/snippet.py index cfe2ef9b..51722f76 100644 --- a/examples/snippets/numbers/callback_configuration/update/snippet.py +++ b/examples/snippets/numbers/event_destinations/update/snippet.py @@ -18,8 +18,8 @@ ) hmac_secret = "NEW_HMAC_SECRET" -response = sinch_client.numbers.callback_configuration.update( +response = sinch_client.numbers.event_destinations.update( hmac_secret=hmac_secret ) -print("Updated callback configuration:\n", response) +print("Updated event destination configuration:\n", response) diff --git a/sinch/domains/numbers/api/v1/__init__.py b/sinch/domains/numbers/api/v1/__init__.py index 15b4bde9..79f11071 100644 --- a/sinch/domains/numbers/api/v1/__init__.py +++ b/sinch/domains/numbers/api/v1/__init__.py @@ -5,8 +5,8 @@ from sinch.domains.numbers.api.v1.available_regions_apis import ( AvailableRegions, ) -from sinch.domains.numbers.api.v1.callback_configuration_apis import ( - CallbackConfiguration, +from sinch.domains.numbers.api.v1.event_destinations_apis import ( + EventDestinations, ) @@ -14,5 +14,5 @@ "ActiveNumbers", "AvailableNumbers", "AvailableRegions", - "CallbackConfiguration", + "EventDestinations", ] diff --git a/sinch/domains/numbers/api/v1/active_numbers_apis.py b/sinch/domains/numbers/api/v1/active_numbers_apis.py index 9cf6dbd3..2f2746f9 100644 --- a/sinch/domains/numbers/api/v1/active_numbers_apis.py +++ b/sinch/domains/numbers/api/v1/active_numbers_apis.py @@ -61,7 +61,7 @@ def update( display_name: Optional[str] = None, sms_configuration: Optional[SmsConfigurationDict] = None, voice_configuration: Optional[VoiceConfigurationDict] = None, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, **kwargs, ) -> ActiveNumber: request_data = UpdateNumberConfigurationRequest( @@ -69,7 +69,7 @@ def update( display_name=display_name, sms_configuration=sms_configuration, voice_configuration=voice_configuration, - callback_url=callback_url, + event_destination_target=event_destination_target, **kwargs, ) return self._request(UpdateNumberConfigurationEndpoint, request_data) diff --git a/sinch/domains/numbers/api/v1/available_numbers_apis.py b/sinch/domains/numbers/api/v1/available_numbers_apis.py index cf415f61..a5538a3f 100644 --- a/sinch/domains/numbers/api/v1/available_numbers_apis.py +++ b/sinch/domains/numbers/api/v1/available_numbers_apis.py @@ -66,14 +66,14 @@ def rent( phone_number: str, sms_configuration: Optional[SmsConfigurationDict] = None, voice_configuration: Optional[VoiceConfigurationDict] = None, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, **kwargs, ) -> ActiveNumber: request_data = RentNumberRequest( phone_number=phone_number, sms_configuration=sms_configuration, voice_configuration=voice_configuration, - callback_url=callback_url, + event_destination_target=event_destination_target, **kwargs, ) return self._request(RentNumberEndpoint, request_data) @@ -86,7 +86,7 @@ def rent_any( capabilities: Optional[List[CapabilityType]] = None, sms_configuration: Optional[SmsConfigurationDict] = None, voice_configuration: Optional[VoiceConfigurationDict] = None, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, **kwargs, ) -> ActiveNumber: request_data = RentAnyNumberRequest( @@ -96,7 +96,7 @@ def rent_any( capabilities=capabilities, sms_configuration=sms_configuration, voice_configuration=voice_configuration, - callback_url=callback_url, + event_destination_target=event_destination_target, **kwargs, ) return self._request(RentAnyNumberEndpoint, request_data) diff --git a/sinch/domains/numbers/api/v1/callback_configuration_apis.py b/sinch/domains/numbers/api/v1/callback_configuration_apis.py deleted file mode 100644 index f4d38c67..00000000 --- a/sinch/domains/numbers/api/v1/callback_configuration_apis.py +++ /dev/null @@ -1,55 +0,0 @@ -from sinch.domains.numbers.api.v1.base import BaseNumbers -from sinch.domains.numbers.api.v1.internal import ( - GetCallbackConfigurationEndpoint, - UpdateCallbackConfigurationEndpoint, -) -from sinch.domains.numbers.models.v1.internal import ( - UpdateCallbackConfigurationRequest, -) -from sinch.domains.numbers.models.v1.internal.base import ( - BaseModelConfigurationRequest, -) -from sinch.domains.numbers.models.v1.response import ( - CallbackConfigurationResponse, -) - - -class CallbackConfiguration(BaseNumbers): - def get(self, **kwargs) -> CallbackConfigurationResponse: - """ - Returns the callback configuration for the specified project - - :param kwargs: Additional parameters for the request. - :type kwargs: dict - - :returns: The callback configuration for the project. - :rtype: NumbersCallbackConfigResponse - - For detailed documentation, visit: https://developers.sinch.com - """ - request_data = None - if kwargs: - request_data = BaseModelConfigurationRequest(**kwargs) - return self._request(GetCallbackConfigurationEndpoint, request_data) - - def update( - self, hmac_secret: str, **kwargs - ) -> CallbackConfigurationResponse: - """ - Updates the callback configuration for the specified project - - :param hmac_secret: The HMAC secret used to sign the callback requests. - :type hmac_secret: str - - :param kwargs: Additional parameters for the request. - :type kwargs: dict - - :returns: The updated callback configuration for the project. - :rtype: NumbersCallbackConfigResponse - - For detailed documentation, visit https://developers.sinch.com - """ - request_data = UpdateCallbackConfigurationRequest( - hmac_secret=hmac_secret, **kwargs - ) - return self._request(UpdateCallbackConfigurationEndpoint, request_data) diff --git a/sinch/domains/numbers/api/v1/event_destinations_apis.py b/sinch/domains/numbers/api/v1/event_destinations_apis.py new file mode 100644 index 00000000..e84a8056 --- /dev/null +++ b/sinch/domains/numbers/api/v1/event_destinations_apis.py @@ -0,0 +1,53 @@ +from sinch.domains.numbers.api.v1.base import BaseNumbers +from sinch.domains.numbers.api.v1.internal import ( + GetEventDestinationEndpoint, + UpdateEventDestinationEndpoint, +) +from sinch.domains.numbers.models.v1.internal import ( + UpdateEventDestinationRequest, +) +from sinch.domains.numbers.models.v1.internal.base import ( + BaseModelConfigurationRequest, +) +from sinch.domains.numbers.models.v1.response import ( + EventDestinationResponse, +) + + +class EventDestinations(BaseNumbers): + def get(self, **kwargs) -> EventDestinationResponse: + """ + Returns the event destination configuration for the specified project + + :param kwargs: Additional parameters for the request. + :type kwargs: dict + + :returns: The event destination configuration for the project. + :rtype: EventDestinationResponse + + For detailed documentation, visit: https://developers.sinch.com + """ + request_data = None + if kwargs: + request_data = BaseModelConfigurationRequest(**kwargs) + return self._request(GetEventDestinationEndpoint, request_data) + + def update(self, hmac_secret: str, **kwargs) -> EventDestinationResponse: + """ + Updates the event destination configuration for the specified project + + :param hmac_secret: The HMAC secret used to sign the event destination requests. + :type hmac_secret: str + + :param kwargs: Additional parameters for the request. + :type kwargs: dict + + :returns: The updated event destination configuration for the project. + :rtype: EventDestinationResponse + + For detailed documentation, visit https://developers.sinch.com + """ + request_data = UpdateEventDestinationRequest( + hmac_secret=hmac_secret, **kwargs + ) + return self._request(UpdateEventDestinationEndpoint, request_data) diff --git a/sinch/domains/numbers/api/v1/internal/__init__.py b/sinch/domains/numbers/api/v1/internal/__init__.py index 083ffb0a..3e42b294 100644 --- a/sinch/domains/numbers/api/v1/internal/__init__.py +++ b/sinch/domains/numbers/api/v1/internal/__init__.py @@ -13,14 +13,14 @@ from sinch.domains.numbers.api.v1.internal.available_regions_endpoints import ( ListAvailableRegionsEndpoint, ) -from sinch.domains.numbers.api.v1.internal.callback_configuration_endpoints import ( - GetCallbackConfigurationEndpoint, - UpdateCallbackConfigurationEndpoint, +from sinch.domains.numbers.api.v1.internal.event_destinations_endpoints import ( + GetEventDestinationEndpoint, + UpdateEventDestinationEndpoint, ) __all__ = [ "AvailableNumbersEndpoint", - "GetCallbackConfigurationEndpoint", + "GetEventDestinationEndpoint", "GetNumberConfigurationEndpoint", "ListActiveNumbersEndpoint", "ListAvailableRegionsEndpoint", @@ -28,6 +28,6 @@ "RentNumberEndpoint", "RentAnyNumberEndpoint", "SearchForNumberEndpoint", - "UpdateCallbackConfigurationEndpoint", + "UpdateEventDestinationEndpoint", "UpdateNumberConfigurationEndpoint", ] diff --git a/sinch/domains/numbers/api/v1/internal/callback_configuration_endpoints.py b/sinch/domains/numbers/api/v1/internal/event_destinations_endpoints.py similarity index 71% rename from sinch/domains/numbers/api/v1/internal/callback_configuration_endpoints.py rename to sinch/domains/numbers/api/v1/internal/event_destinations_endpoints.py index e50dac4f..f2fe06bd 100644 --- a/sinch/domains/numbers/api/v1/internal/callback_configuration_endpoints.py +++ b/sinch/domains/numbers/api/v1/internal/event_destinations_endpoints.py @@ -7,16 +7,16 @@ ) from sinch.domains.numbers.api.v1.internal.base import NumbersEndpoint from sinch.domains.numbers.models.v1.internal import ( - UpdateCallbackConfigurationRequest, + UpdateEventDestinationRequest, ) from sinch.domains.numbers.models.v1.response import ( - CallbackConfigurationResponse, + EventDestinationResponse, ) -class GetCallbackConfigurationEndpoint(NumbersEndpoint): +class GetEventDestinationEndpoint(NumbersEndpoint): """ - Endpoint to get the callbacks configuration for a project. + Endpoint to get the event destination configuration for a project. """ ENDPOINT_URL = "{origin}/v1/projects/{project_id}/callbackConfiguration" @@ -24,7 +24,7 @@ class GetCallbackConfigurationEndpoint(NumbersEndpoint): HTTP_AUTHENTICATION = HTTPAuthentication.OAUTH.value def __init__(self, project_id: str, request_data=None): - super(GetCallbackConfigurationEndpoint, self).__init__( + super(GetEventDestinationEndpoint, self).__init__( project_id, request_data ) self.project_id = project_id @@ -32,7 +32,7 @@ def __init__(self, project_id: str, request_data=None): def build_url(self, sinch) -> str: if self.request_data: - super(GetCallbackConfigurationEndpoint, self).build_url(sinch) + super(GetEventDestinationEndpoint, self).build_url(sinch) return self.ENDPOINT_URL.format( origin=sinch.configuration.numbers_origin, project_id=self.project_id, @@ -47,11 +47,9 @@ def build_query_params(self) -> dict: def handle_response( self, response: HTTPResponse - ) -> CallbackConfigurationResponse: + ) -> EventDestinationResponse: try: - super(GetCallbackConfigurationEndpoint, self).handle_response( - response - ) + super(GetEventDestinationEndpoint, self).handle_response(response) except NumbersException as e: raise NumberNotFoundException( message=e.args[0], @@ -59,13 +57,13 @@ def handle_response( is_from_server=e.is_from_server, ) return self.process_response_model( - response.body, CallbackConfigurationResponse + response.body, EventDestinationResponse ) -class UpdateCallbackConfigurationEndpoint(NumbersEndpoint): +class UpdateEventDestinationEndpoint(NumbersEndpoint): """ - Endpoint to update the callbacks configuration for a project. + Endpoint to update the event destination configuration for a project. """ ENDPOINT_URL = "{origin}/v1/projects/{project_id}/callbackConfiguration" @@ -73,9 +71,9 @@ class UpdateCallbackConfigurationEndpoint(NumbersEndpoint): HTTP_AUTHENTICATION = HTTPAuthentication.OAUTH.value def __init__( - self, project_id: str, request_data: UpdateCallbackConfigurationRequest + self, project_id: str, request_data: UpdateEventDestinationRequest ): - super(UpdateCallbackConfigurationEndpoint, self).__init__( + super(UpdateEventDestinationEndpoint, self).__init__( project_id, request_data ) self.project_id = project_id @@ -89,9 +87,9 @@ def request_body(self): def handle_response( self, response: HTTPResponse - ) -> CallbackConfigurationResponse: + ) -> EventDestinationResponse: try: - super(UpdateCallbackConfigurationEndpoint, self).handle_response( + super(UpdateEventDestinationEndpoint, self).handle_response( response ) except NumbersException as e: @@ -101,5 +99,5 @@ def handle_response( is_from_server=e.is_from_server, ) return self.process_response_model( - response.body, CallbackConfigurationResponse + response.body, EventDestinationResponse ) diff --git a/sinch/domains/numbers/models/v1/internal/__init__.py b/sinch/domains/numbers/models/v1/internal/__init__.py index a73547d5..c69e43e6 100644 --- a/sinch/domains/numbers/models/v1/internal/__init__.py +++ b/sinch/domains/numbers/models/v1/internal/__init__.py @@ -28,8 +28,8 @@ from sinch.domains.numbers.models.v1.internal.sms_configuration_request import ( SmsConfigurationRequest, ) -from sinch.domains.numbers.models.v1.internal.update_callback_configuration_request import ( - UpdateCallbackConfigurationRequest, +from sinch.domains.numbers.models.v1.internal.update_event_destination_request import ( + UpdateEventDestinationRequest, ) from sinch.domains.numbers.models.v1.internal.update_number_configuration_request import ( UpdateNumberConfigurationRequest, @@ -52,7 +52,7 @@ "RentAnyNumberRequest", "RentNumberRequest", "SmsConfigurationRequest", - "UpdateCallbackConfigurationRequest", + "UpdateEventDestinationRequest", "UpdateNumberConfigurationRequest", "VoiceConfigurationCustom", "VoiceConfigurationEST", diff --git a/sinch/domains/numbers/models/v1/internal/rent_any_number_request.py b/sinch/domains/numbers/models/v1/internal/rent_any_number_request.py index d0a43a6f..85b88f73 100644 --- a/sinch/domains/numbers/models/v1/internal/rent_any_number_request.py +++ b/sinch/domains/numbers/models/v1/internal/rent_any_number_request.py @@ -26,7 +26,7 @@ class RentAnyNumberRequest(BaseModelConfigurationRequest): voice_configuration: Optional[Dict[str, Any]] = Field( default=None, alias="voiceConfiguration" ) - callback_url: Optional[StrictStr] = Field( + event_destination_target: Optional[StrictStr] = Field( default=None, alias="callbackUrl" ) diff --git a/sinch/domains/numbers/models/v1/internal/rent_number_request.py b/sinch/domains/numbers/models/v1/internal/rent_number_request.py index 14b16b1e..9c2980b6 100644 --- a/sinch/domains/numbers/models/v1/internal/rent_number_request.py +++ b/sinch/domains/numbers/models/v1/internal/rent_number_request.py @@ -20,7 +20,7 @@ class RentNumberRequest(BaseModelConfigurationRequest): voice_configuration: Optional[Dict] = Field( default=None, alias="voiceConfiguration" ) - callback_url: Optional[StrictStr] = Field( + event_destination_target: Optional[StrictStr] = Field( default=None, alias="callbackUrl" ) diff --git a/sinch/domains/numbers/models/v1/internal/update_callback_configuration_request.py b/sinch/domains/numbers/models/v1/internal/update_event_destination_request.py similarity index 76% rename from sinch/domains/numbers/models/v1/internal/update_callback_configuration_request.py rename to sinch/domains/numbers/models/v1/internal/update_event_destination_request.py index 33b52ae7..6f2e5abc 100644 --- a/sinch/domains/numbers/models/v1/internal/update_callback_configuration_request.py +++ b/sinch/domains/numbers/models/v1/internal/update_event_destination_request.py @@ -5,5 +5,5 @@ ) -class UpdateCallbackConfigurationRequest(BaseModelConfigurationRequest): +class UpdateEventDestinationRequest(BaseModelConfigurationRequest): hmac_secret: Optional[StrictStr] = Field(default=None, alias="hmacSecret") diff --git a/sinch/domains/numbers/models/v1/internal/update_number_configuration_request.py b/sinch/domains/numbers/models/v1/internal/update_number_configuration_request.py index 07ebef0e..5586ecdf 100644 --- a/sinch/domains/numbers/models/v1/internal/update_number_configuration_request.py +++ b/sinch/domains/numbers/models/v1/internal/update_number_configuration_request.py @@ -22,7 +22,7 @@ class UpdateNumberConfigurationRequest(BaseModelConfigurationRequest): voice_configuration: Optional[Dict] = Field( default=None, alias="voiceConfiguration" ) - callback_url: Optional[StrictStr] = Field( + event_destination_target: Optional[StrictStr] = Field( default=None, alias="callbackUrl" ) diff --git a/sinch/domains/numbers/models/v1/response/__init__.py b/sinch/domains/numbers/models/v1/response/__init__.py index 5852d55f..d96ad096 100644 --- a/sinch/domains/numbers/models/v1/response/__init__.py +++ b/sinch/domains/numbers/models/v1/response/__init__.py @@ -5,13 +5,13 @@ from sinch.domains.numbers.models.v1.response.available_region import ( AvailableRegion, ) -from sinch.domains.numbers.models.v1.response.callback_configuration_response import ( - CallbackConfigurationResponse, +from sinch.domains.numbers.models.v1.response.event_destination_response import ( + EventDestinationResponse, ) __all__ = [ "ActiveNumber", "AvailableNumber", "AvailableRegion", - "CallbackConfigurationResponse", + "EventDestinationResponse", ] diff --git a/sinch/domains/numbers/models/v1/response/active_number.py b/sinch/domains/numbers/models/v1/response/active_number.py index c7072c45..0306abab 100644 --- a/sinch/domains/numbers/models/v1/response/active_number.py +++ b/sinch/domains/numbers/models/v1/response/active_number.py @@ -43,6 +43,6 @@ class ActiveNumber(BaseModelConfigurationResponse): voice_configuration: Optional[VoiceConfiguration] = Field( default=None, alias="voiceConfiguration" ) - callback_url: Optional[StrictStr] = Field( + event_destination_target: Optional[StrictStr] = Field( default=None, alias="callbackUrl" ) diff --git a/sinch/domains/numbers/models/v1/response/callback_configuration_response.py b/sinch/domains/numbers/models/v1/response/event_destination_response.py similarity index 82% rename from sinch/domains/numbers/models/v1/response/callback_configuration_response.py rename to sinch/domains/numbers/models/v1/response/event_destination_response.py index b1a26be6..b94d915a 100644 --- a/sinch/domains/numbers/models/v1/response/callback_configuration_response.py +++ b/sinch/domains/numbers/models/v1/response/event_destination_response.py @@ -5,6 +5,6 @@ ) -class CallbackConfigurationResponse(BaseModelConfigurationResponse): +class EventDestinationResponse(BaseModelConfigurationResponse): project_id: Optional[StrictStr] = Field(default=None, alias="projectId") hmac_secret: Optional[StrictStr] = Field(default=None, alias="hmacSecret") diff --git a/sinch/domains/numbers/webhooks/__init__.py b/sinch/domains/numbers/sinch_events/__init__.py similarity index 100% rename from sinch/domains/numbers/webhooks/__init__.py rename to sinch/domains/numbers/sinch_events/__init__.py diff --git a/sinch/domains/numbers/sinch_events/v1/__init__.py b/sinch/domains/numbers/sinch_events/v1/__init__.py new file mode 100644 index 00000000..50027ca3 --- /dev/null +++ b/sinch/domains/numbers/sinch_events/v1/__init__.py @@ -0,0 +1,5 @@ +from sinch.domains.numbers.sinch_events.v1.sinch_events import ( + SinchEvents, +) + +__all__ = ["SinchEvents"] diff --git a/sinch/domains/numbers/sinch_events/v1/events/__init__.py b/sinch/domains/numbers/sinch_events/v1/events/__init__.py new file mode 100644 index 00000000..94728f9c --- /dev/null +++ b/sinch/domains/numbers/sinch_events/v1/events/__init__.py @@ -0,0 +1,5 @@ +from sinch.domains.numbers.sinch_events.v1.events.number_sinch_event import ( + NumberSinchEvent, +) + +__all__ = ["NumberSinchEvent"] diff --git a/sinch/domains/numbers/webhooks/v1/events/numbers_webhooks_event.py b/sinch/domains/numbers/sinch_events/v1/events/number_sinch_event.py similarity index 94% rename from sinch/domains/numbers/webhooks/v1/events/numbers_webhooks_event.py rename to sinch/domains/numbers/sinch_events/v1/events/number_sinch_event.py index 2070b4f3..43633e2c 100644 --- a/sinch/domains/numbers/webhooks/v1/events/numbers_webhooks_event.py +++ b/sinch/domains/numbers/sinch_events/v1/events/number_sinch_event.py @@ -1,10 +1,10 @@ from datetime import datetime from typing import Optional, Union, Literal from pydantic import Field, StrictStr -from sinch.domains.numbers.webhooks.v1.internal import WebhookEvent +from sinch.domains.numbers.sinch_events.v1.internal import SinchEvent -class NumbersWebhooksEvent(WebhookEvent): +class NumberSinchEvent(SinchEvent): event_id: Optional[StrictStr] = Field(default=None, alias="eventId") timestamp: Optional[datetime] = Field(default=None) project_id: Optional[StrictStr] = Field(default=None, alias="projectId") diff --git a/sinch/domains/numbers/sinch_events/v1/internal/__init__.py b/sinch/domains/numbers/sinch_events/v1/internal/__init__.py new file mode 100644 index 00000000..6af7aa73 --- /dev/null +++ b/sinch/domains/numbers/sinch_events/v1/internal/__init__.py @@ -0,0 +1,5 @@ +from sinch.domains.numbers.sinch_events.v1.internal.sinch_event import ( + SinchEvent, +) + +__all__ = ["SinchEvent"] diff --git a/sinch/domains/numbers/webhooks/v1/internal/webhook_event.py b/sinch/domains/numbers/sinch_events/v1/internal/sinch_event.py similarity index 62% rename from sinch/domains/numbers/webhooks/v1/internal/webhook_event.py rename to sinch/domains/numbers/sinch_events/v1/internal/sinch_event.py index 6c9cf47f..5ec2f5ce 100644 --- a/sinch/domains/numbers/webhooks/v1/internal/webhook_event.py +++ b/sinch/domains/numbers/sinch_events/v1/internal/sinch_event.py @@ -3,7 +3,7 @@ ) -# Alias for NumbersWebhooksEvent used for request modeling. +# Base for NumberSinchEvent used for request modeling. # Not to be confused with a response as in BaseModelConfigurationResponse. -class WebhookEvent(BaseModelConfigurationResponse): +class SinchEvent(BaseModelConfigurationResponse): pass diff --git a/sinch/domains/numbers/webhooks/v1/numbers_webhooks.py b/sinch/domains/numbers/sinch_events/v1/sinch_events.py similarity index 89% rename from sinch/domains/numbers/webhooks/v1/numbers_webhooks.py rename to sinch/domains/numbers/sinch_events/v1/sinch_events.py index b804fb74..d591acbe 100644 --- a/sinch/domains/numbers/webhooks/v1/numbers_webhooks.py +++ b/sinch/domains/numbers/sinch_events/v1/sinch_events.py @@ -7,10 +7,10 @@ parse_json, normalize_iso_timestamp, ) -from sinch.domains.numbers.webhooks.v1.events import NumbersWebhooksEvent +from sinch.domains.numbers.sinch_events.v1.events import NumberSinchEvent -class NumbersWebhooks: +class SinchEvents: def __init__(self, callback_secret: str): self.callback_secret = callback_secret @@ -42,9 +42,9 @@ def parse_event( self, event_body: Union[str, bytes, Dict[str, Any]], headers: Optional[Dict[str, str]] = None, - ) -> NumbersWebhooksEvent: + ) -> NumberSinchEvent: """ - Parses the event payload into a NumbersWebhooksEvent object. + Parses the event payload into a NumberSinchEvent object. Handles a known issue where the server omits timezone information from the ``timestamp`` field. If the timezone is missing, the method assumes @@ -55,7 +55,7 @@ def parse_event( :param headers: Request headers (used to decode charset when event_body is bytes). :type headers: Optional[Dict[str, str]] :returns: A parsed Pydantic object with a timezone-aware ``timestamp``. - :rtype: NumbersWebhooksEvent + :rtype: NumberSinchEvent """ if isinstance(event_body, bytes): event_body = parse_json(decode_payload(event_body, headers)) @@ -65,6 +65,6 @@ def parse_event( if timestamp: event_body["timestamp"] = normalize_iso_timestamp(timestamp) try: - return NumbersWebhooksEvent(**event_body) + return NumberSinchEvent(**event_body) except Exception as e: raise ValueError(f"Failed to parse event body: {e}") diff --git a/sinch/domains/numbers/virtual_numbers.py b/sinch/domains/numbers/virtual_numbers.py index fd03cd6c..7827db8b 100644 --- a/sinch/domains/numbers/virtual_numbers.py +++ b/sinch/domains/numbers/virtual_numbers.py @@ -3,7 +3,7 @@ ActiveNumbers, AvailableNumbers, AvailableRegions, - CallbackConfiguration, + EventDestinations, ) from sinch.core.pagination import Paginator from sinch.domains.numbers.models.v1.response import ( @@ -22,7 +22,7 @@ VoiceConfigurationESTDict, NumberPatternDict, ) -from sinch.domains.numbers.webhooks.v1 import NumbersWebhooks +from sinch.domains.numbers.sinch_events.v1 import SinchEvents class VirtualNumbers: @@ -36,21 +36,21 @@ class VirtualNumbers: def __init__(self, sinch): self._sinch = sinch self.regions = AvailableRegions(self._sinch) - self.callback_configuration = CallbackConfiguration(self._sinch) + self.event_destinations = EventDestinations(self._sinch) self._active = ActiveNumbers(self._sinch) self._available = AvailableNumbers(self._sinch) - def webhooks(self, callback_secret: str) -> NumbersWebhooks: + def sinch_events(self, callback_secret: str) -> SinchEvents: """ - Create a Numbers webhooks handler with the specified callback secret. + Create a Numbers Sinch Events handler with the specified callback secret. :param callback_secret: Secret used for webhook validation. :type callback_secret: str - :returns: A configured webhooks handler - :rtype: NumbersWebhooks + :returns: A configured Sinch Events handler + :rtype: SinchEvents """ - return NumbersWebhooks(callback_secret) + return SinchEvents(callback_secret) # ====== High-Level Convenience Methods ====== @@ -120,7 +120,7 @@ def update( sms_configuration: SmsConfigurationDict, voice_configuration: VoiceConfigurationESTDict, display_name: Optional[str] = None, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, ) -> ActiveNumber: pass @@ -131,7 +131,7 @@ def update( sms_configuration: SmsConfigurationDict, voice_configuration: VoiceConfigurationFAXDict, display_name: Optional[str] = None, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, ) -> ActiveNumber: pass @@ -142,7 +142,7 @@ def update( sms_configuration: SmsConfigurationDict, voice_configuration: VoiceConfigurationRTCDict, display_name: Optional[str] = None, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, ) -> ActiveNumber: pass @@ -152,7 +152,7 @@ def update( display_name: Optional[str] = None, sms_configuration: Optional[SmsConfigurationDict] = None, voice_configuration: Optional[VoiceConfigurationDict] = None, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, **kwargs, ) -> ActiveNumber: """ @@ -174,8 +174,8 @@ def update( - ``VoiceConfigurationFAXDict``: type ``'FAX'`` with a ``service_id`` field. :type voice_configuration: Optional[VoiceConfigurationDict] - :param callback_url: The callback URL for the virtual number. - :type callback_url: Optional[str] + :param event_destination_target: The event destination URL for the virtual number. + :type event_destination_target: Optional[str] :param kwargs: Additional parameters for the request. :type kwargs: dict @@ -187,7 +187,7 @@ def update( display_name=display_name, sms_configuration=sms_configuration, voice_configuration=voice_configuration, - callback_url=callback_url, + event_destination_target=event_destination_target, **kwargs, ) @@ -252,7 +252,7 @@ def rent( phone_number: str, sms_configuration: SmsConfigurationDict, voice_configuration: VoiceConfigurationESTDict, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, ) -> ActiveNumber: pass @@ -262,7 +262,7 @@ def rent( phone_number: str, sms_configuration: SmsConfigurationDict, voice_configuration: VoiceConfigurationFAXDict, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, ) -> ActiveNumber: pass @@ -272,7 +272,7 @@ def rent( phone_number: str, sms_configuration: SmsConfigurationDict, voice_configuration: VoiceConfigurationRTCDict, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, ) -> ActiveNumber: pass @@ -281,7 +281,7 @@ def rent( phone_number: str, sms_configuration: Optional[SmsConfigurationDict] = None, voice_configuration: Optional[VoiceConfigurationDict] = None, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, **kwargs, ) -> ActiveNumber: """ @@ -296,8 +296,8 @@ def rent( - ``VoiceConfigurationESTDict``: type ``'EST'`` with a ``trunk_id`` field. - ``VoiceConfigurationFAXDict``: type ``'FAX'`` with a ``service_id`` field. :type voice_configuration: Optional[VoiceConfigurationDict] - :param callback_url: The callback URL to be called. - :type callback_url: Optional[str] + :param event_destination_target: The event destination URL to be called. + :type event_destination_target: Optional[str] :param kwargs: Additional parameters for the request. :type kwargs: dict @@ -310,7 +310,7 @@ def rent( phone_number=phone_number, sms_configuration=sms_configuration, voice_configuration=voice_configuration, - callback_url=callback_url, + event_destination_target=event_destination_target, **kwargs, ) @@ -323,7 +323,7 @@ def rent_any( voice_configuration: VoiceConfigurationRTCDict, number_pattern: NumberPatternDict, capabilities: Optional[CapabilityType] = None, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, ) -> ActiveNumber: pass @@ -336,7 +336,7 @@ def rent_any( voice_configuration: VoiceConfigurationFAXDict, number_pattern: NumberPatternDict, capabilities: Optional[List[CapabilityType]] = None, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, ) -> ActiveNumber: pass @@ -349,7 +349,7 @@ def rent_any( voice_configuration: VoiceConfigurationESTDict, number_pattern: NumberPatternDict, capabilities: Optional[List[CapabilityType]] = None, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, ) -> ActiveNumber: pass @@ -361,7 +361,7 @@ def rent_any( capabilities: Optional[List[CapabilityType]] = None, sms_configuration: Optional[SmsConfigurationDict] = None, voice_configuration: Optional[VoiceConfigurationDict] = None, - callback_url: Optional[str] = None, + event_destination_target: Optional[str] = None, **kwargs, ) -> ActiveNumber: """ @@ -389,8 +389,8 @@ def rent_any( - ``VoiceConfigurationFAXDict``: type ``'FAX'`` with a ``service_id`` field. :type voice_configuration: Optional[VoiceConfigurationDict] - :param callback_url: The callback URL to receive notifications. - :type callback_url: Optional[str] + :param event_destination_target: The event destination URL to receive notifications. + :type event_destination_target: Optional[str] :param kwargs: Additional parameters for the request. :type kwargs: dict @@ -407,7 +407,7 @@ def rent_any( capabilities=capabilities, sms_configuration=sms_configuration, voice_configuration=voice_configuration, - callback_url=callback_url, + event_destination_target=event_destination_target, **kwargs, ) diff --git a/sinch/domains/numbers/webhooks/v1/__init__.py b/sinch/domains/numbers/webhooks/v1/__init__.py deleted file mode 100644 index c6b1d66f..00000000 --- a/sinch/domains/numbers/webhooks/v1/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from sinch.domains.numbers.webhooks.v1.numbers_webhooks import NumbersWebhooks - -__all__ = ["NumbersWebhooks"] diff --git a/sinch/domains/numbers/webhooks/v1/events/__init__.py b/sinch/domains/numbers/webhooks/v1/events/__init__.py deleted file mode 100644 index b5fb44cc..00000000 --- a/sinch/domains/numbers/webhooks/v1/events/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from sinch.domains.numbers.webhooks.v1.events.numbers_webhooks_event import ( - NumbersWebhooksEvent, -) - -__all__ = ["NumbersWebhooksEvent"] diff --git a/sinch/domains/numbers/webhooks/v1/internal/__init__.py b/sinch/domains/numbers/webhooks/v1/internal/__init__.py deleted file mode 100644 index 892d0749..00000000 --- a/sinch/domains/numbers/webhooks/v1/internal/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from sinch.domains.numbers.webhooks.v1.internal.webhook_event import ( - WebhookEvent, -) - -__all__ = ["WebhookEvent"] diff --git a/tests/e2e/numbers/features/steps/callback-configuration.steps.py b/tests/e2e/numbers/features/steps/callback-configuration.steps.py index 522c4634..b7e7624f 100644 --- a/tests/e2e/numbers/features/steps/callback-configuration.steps.py +++ b/tests/e2e/numbers/features/steps/callback-configuration.steps.py @@ -15,7 +15,7 @@ def step_callback_config_service_is_available(context): @when('I send a request to retrieve the callback configuration') def step_retrieve_callback_configuration(context): - context.response = context.numbers.callback_configuration.get() + context.response = context.numbers.event_destinations.get() @then('the response contains the project\'s callback configuration') @@ -27,7 +27,7 @@ def step_check_callback_configuration(context): @when('I send a request to update the callback configuration with the secret "{hmac_secret}"') def step_update_callback_configuration(context, hmac_secret): try: - context.response = context.numbers.callback_configuration.update(hmac_secret=hmac_secret) + context.response = context.numbers.event_destinations.update(hmac_secret=hmac_secret) context.error = None except NumberNotFoundException as e: context.error = e diff --git a/tests/e2e/numbers/features/steps/numbers.steps.py b/tests/e2e/numbers/features/steps/numbers.steps.py index 94bec075..25d7b192 100644 --- a/tests/e2e/numbers/features/steps/numbers.steps.py +++ b/tests/e2e/numbers/features/steps/numbers.steps.py @@ -103,7 +103,7 @@ def step_validate_rented_number(context): '2024-06-06T14:42:42.022227+00:00' ).astimezone(tz=timezone.utc) assert data.expire_at is None - assert data.callback_url == '' + assert data.event_destination_target == '' assert data.sms_configuration.service_plan_id == '' assert data.sms_configuration.campaign_id == '' assert data.sms_configuration.scheduled_provisioning.service_plan_id == 'SpaceMonkeySquadron' @@ -218,7 +218,7 @@ def step_when_update_phone_number(context, phone_number): 'type': 'FAX', 'service_id': '01W4FFL35P4NC4K35FAXSERVICE' }, - callback_url='https://my-callback-server.com/numbers' + event_destination_target='https://my-callback-server.com/numbers' ) @@ -247,7 +247,7 @@ def step_then_response_contains_updated_number(context): assert data.voice_configuration.scheduled_voice_provisioning.last_updated_time == datetime.fromisoformat( '2024-06-06T20:02:20.437509+00:00' ).astimezone(tz=timezone.utc) - assert data.callback_url == 'https://my-callback-server.com/numbers' + assert data.event_destination_target == 'https://my-callback-server.com/numbers' @when('I send a request to retrieve the phone number "{phone_number}"') diff --git a/tests/e2e/numbers/features/steps/webhooks.steps.py b/tests/e2e/numbers/features/steps/webhooks.steps.py index 28409ff2..cb93cbbb 100644 --- a/tests/e2e/numbers/features/steps/webhooks.steps.py +++ b/tests/e2e/numbers/features/steps/webhooks.steps.py @@ -8,7 +8,7 @@ @given('the Numbers Webhooks handler is available') def step_webhook_handler_is_available(context): - context.numbers_webhook = context.sinch.numbers.webhooks(SINCH_NUMBERS_CALLBACK_SECRET) + context.numbers_webhook = context.sinch.numbers.sinch_events(SINCH_NUMBERS_CALLBACK_SECRET) @when('I send a request to trigger the "{status}" for "{event_type}" event') diff --git a/tests/unit/domains/numbers/v1/endpoints/active/test_get_active_numbers_endpoint.py b/tests/unit/domains/numbers/v1/endpoints/active/test_get_active_numbers_endpoint.py index 3f5dc4d9..ee3444a4 100644 --- a/tests/unit/domains/numbers/v1/endpoints/active/test_get_active_numbers_endpoint.py +++ b/tests/unit/domains/numbers/v1/endpoints/active/test_get_active_numbers_endpoint.py @@ -69,4 +69,4 @@ def test_handle_response_expects_correct_mapping(endpoint, mock_response): expected_expire_at = ( datetime(2025, 2, 28, 14, 4, 26, 190127, tzinfo=timezone.utc)) assert parsed_response.expire_at == expected_expire_at - assert parsed_response.callback_url == "https://yourcallback/numbers" + assert parsed_response.event_destination_target == "https://yourcallback/numbers" diff --git a/tests/unit/domains/numbers/v1/endpoints/active/test_list_active_numbers_endpoint.py b/tests/unit/domains/numbers/v1/endpoints/active/test_list_active_numbers_endpoint.py index e674d5c6..d291aeab 100644 --- a/tests/unit/domains/numbers/v1/endpoints/active/test_list_active_numbers_endpoint.py +++ b/tests/unit/domains/numbers/v1/endpoints/active/test_list_active_numbers_endpoint.py @@ -102,6 +102,6 @@ def test_handle_response_expects_correct_mapping(endpoint, mock_response): 2025, 2, 28, 14, 4, 26, 190127, tzinfo=timezone.utc ) assert number.expire_at == expected_expire_at - assert number.callback_url == "https://yourcallback/numbers" + assert number.event_destination_target == "https://yourcallback/numbers" assert parsed_response.next_page_token == "CgtwaG9uoLnNDQzajQSDCsxMzE1OTA0MzM1OQ==" assert parsed_response.total_size == 10 diff --git a/tests/unit/domains/numbers/v1/endpoints/active/test_release_active_numbers_endpoint.py b/tests/unit/domains/numbers/v1/endpoints/active/test_release_active_numbers_endpoint.py index 1588b3ba..f09c88a3 100644 --- a/tests/unit/domains/numbers/v1/endpoints/active/test_release_active_numbers_endpoint.py +++ b/tests/unit/domains/numbers/v1/endpoints/active/test_release_active_numbers_endpoint.py @@ -69,4 +69,4 @@ def test_handle_response_expects_correct_mapping(endpoint, mock_response): expected_expire_at = ( datetime(2025, 2, 28, 14, 4, 26, 190127, tzinfo=timezone.utc)) assert parsed_response.expire_at == expected_expire_at - assert parsed_response.callback_url == "https://yourcallback/numbers" + assert parsed_response.event_destination_target == "https://yourcallback/numbers" diff --git a/tests/unit/domains/numbers/v1/endpoints/active/test_update_active_numbers_endpoint.py b/tests/unit/domains/numbers/v1/endpoints/active/test_update_active_numbers_endpoint.py index 2e4fbbfa..34af95e3 100644 --- a/tests/unit/domains/numbers/v1/endpoints/active/test_update_active_numbers_endpoint.py +++ b/tests/unit/domains/numbers/v1/endpoints/active/test_update_active_numbers_endpoint.py @@ -104,4 +104,4 @@ def test_handle_response_expects_correct_mapping(endpoint, mock_response): expected_expire_at = ( datetime(2025, 2, 28, 14, 4, 26, 190127, tzinfo=timezone.utc)) assert parsed_response.expire_at == expected_expire_at - assert parsed_response.callback_url == "https://yourcallback/numbers" + assert parsed_response.event_destination_target == "https://yourcallback/numbers" diff --git a/tests/unit/domains/numbers/v1/endpoints/available/test_rent_any_number_endpoint.py b/tests/unit/domains/numbers/v1/endpoints/available/test_rent_any_number_endpoint.py index db09d00a..ee521ea0 100644 --- a/tests/unit/domains/numbers/v1/endpoints/available/test_rent_any_number_endpoint.py +++ b/tests/unit/domains/numbers/v1/endpoints/available/test_rent_any_number_endpoint.py @@ -19,7 +19,7 @@ def valid_request_data(): capabilities=["SMS"], sms_configuration={"servicePlanId": "string", "campaignId": "string"}, voice_configuration={"appId": "string"}, - callback_url="https://www.your-callback-server.com/callback", + event_destination_target="https://www.your-callback-server.com/callback", ) @@ -140,4 +140,4 @@ def test_handle_response_expects_valid_mapping(valid_response_data): assert voice_config.scheduled_voice_provisioning.status == "PROVISIONING_STATUS_UNSPECIFIED" assert voice_config.scheduled_voice_provisioning.trunk_id == "string" assert voice_config.app_id == "string" - assert response.callback_url == "https://www.your-callback-server.com/callback" + assert response.event_destination_target == "https://www.your-callback-server.com/callback" diff --git a/tests/unit/domains/numbers/v1/endpoints/callbacks/test_get_numbers_callback_endpoint.py b/tests/unit/domains/numbers/v1/endpoints/event_destination/test_get_event_destination_endpoint.py similarity index 85% rename from tests/unit/domains/numbers/v1/endpoints/callbacks/test_get_numbers_callback_endpoint.py rename to tests/unit/domains/numbers/v1/endpoints/event_destination/test_get_event_destination_endpoint.py index 8555146f..4f8af3b0 100644 --- a/tests/unit/domains/numbers/v1/endpoints/callbacks/test_get_numbers_callback_endpoint.py +++ b/tests/unit/domains/numbers/v1/endpoints/event_destination/test_get_event_destination_endpoint.py @@ -1,8 +1,8 @@ import pytest from sinch.core.models.http_response import HTTPResponse -from sinch.domains.numbers.api.v1.internal import GetCallbackConfigurationEndpoint +from sinch.domains.numbers.api.v1.internal import GetEventDestinationEndpoint from sinch.domains.numbers.models.v1.internal.base import BaseModelConfigurationRequest -from sinch.domains.numbers.models.v1.response import CallbackConfigurationResponse +from sinch.domains.numbers.models.v1.response import EventDestinationResponse @pytest.fixture @@ -19,7 +19,7 @@ def mock_response(): @pytest.fixture def endpoint_empty_request_data(): - return GetCallbackConfigurationEndpoint("test_project_id", request_data=None) + return GetEventDestinationEndpoint("test_project_id", request_data=None) @pytest.fixture @@ -29,7 +29,7 @@ def endpoint_extra_request_data(): "extra_field": "extra value" } request_model = BaseModelConfigurationRequest(**data) - return GetCallbackConfigurationEndpoint("test_project_id", request_data=request_model) + return GetEventDestinationEndpoint("test_project_id", request_data=request_model) endpoint_fixtures = pytest.mark.parametrize("endpoint_fixture", [ @@ -73,6 +73,6 @@ def test_handle_response_expects_correct_mapping(endpoint_fixture, mock_response """ endpoint = request.getfixturevalue(endpoint_fixture) parsed_response = endpoint.handle_response(mock_response) - assert isinstance(parsed_response, CallbackConfigurationResponse) + assert isinstance(parsed_response, EventDestinationResponse) assert parsed_response.project_id == "j55aa9aa-b888-777c-dd6d-ee55e1010101010" assert parsed_response.hmac_secret == "your_hmac_secret" diff --git a/tests/unit/domains/numbers/v1/endpoints/callbacks/test_update_numbers_callback_endpoint.py b/tests/unit/domains/numbers/v1/endpoints/event_destination/test_update_event_destination_endpoint.py similarity index 77% rename from tests/unit/domains/numbers/v1/endpoints/callbacks/test_update_numbers_callback_endpoint.py rename to tests/unit/domains/numbers/v1/endpoints/event_destination/test_update_event_destination_endpoint.py index fe20ed24..0b1871f4 100644 --- a/tests/unit/domains/numbers/v1/endpoints/callbacks/test_update_numbers_callback_endpoint.py +++ b/tests/unit/domains/numbers/v1/endpoints/event_destination/test_update_event_destination_endpoint.py @@ -1,14 +1,14 @@ import json import pytest from sinch.core.models.http_response import HTTPResponse -from sinch.domains.numbers.api.v1.internal import UpdateCallbackConfigurationEndpoint -from sinch.domains.numbers.models.v1.internal import UpdateCallbackConfigurationRequest -from sinch.domains.numbers.models.v1.response import CallbackConfigurationResponse +from sinch.domains.numbers.api.v1.internal import UpdateEventDestinationEndpoint +from sinch.domains.numbers.models.v1.internal import UpdateEventDestinationRequest +from sinch.domains.numbers.models.v1.response import EventDestinationResponse @pytest.fixture def mock_request_data(): - return UpdateCallbackConfigurationRequest( + return UpdateEventDestinationRequest( hmac_secret="your_hmac_secret" ) @@ -35,7 +35,7 @@ def mock_response_body(): @pytest.fixture def endpoint(mock_request_data): - return UpdateCallbackConfigurationEndpoint("test_project_id", mock_request_data) + return UpdateEventDestinationEndpoint("test_project_id", mock_request_data) def test_build_url(endpoint, mock_sinch_client_numbers): @@ -59,6 +59,6 @@ def test_handle_response_expects_correct_mapping(endpoint, mock_response): Check if response is handled and mapped to the appropriate fields correctly. """ parsed_response = endpoint.handle_response(mock_response) - assert isinstance(parsed_response, CallbackConfigurationResponse) + assert isinstance(parsed_response, EventDestinationResponse) assert parsed_response.project_id == "a99aa9aa-b888-777c-dd6d-ee55e5555555" assert parsed_response.hmac_secret == "your_hmac_secret" diff --git a/tests/unit/domains/numbers/v1/models/internal/test_rent_any_number_request_model.py b/tests/unit/domains/numbers/v1/models/internal/test_rent_any_number_request_model.py index 22f1f66f..e615c2fd 100644 --- a/tests/unit/domains/numbers/v1/models/internal/test_rent_any_number_request_model.py +++ b/tests/unit/domains/numbers/v1/models/internal/test_rent_any_number_request_model.py @@ -41,7 +41,7 @@ def test_rent_any_number_request_expects_valid_data(): "type": "RTC", "appId": "string" } - assert request.callback_url == "https://www.your-callback-server.com/callback" + assert request.event_destination_target == "https://www.your-callback-server.com/callback" def test_rent_any_number_request_expects_missing_optional_fields(): @@ -62,4 +62,4 @@ def test_rent_any_number_request_expects_missing_optional_fields(): assert request.capabilities is None assert request.sms_configuration is None assert request.voice_configuration is None - assert request.callback_url is None + assert request.event_destination_target is None diff --git a/tests/unit/domains/numbers/v1/models/internal/test_update_active_numbers_request_model.py b/tests/unit/domains/numbers/v1/models/internal/test_update_active_numbers_request_model.py index 12bf9ca8..4b7c0db3 100644 --- a/tests/unit/domains/numbers/v1/models/internal/test_update_active_numbers_request_model.py +++ b/tests/unit/domains/numbers/v1/models/internal/test_update_active_numbers_request_model.py @@ -31,7 +31,7 @@ def test_update_number_configuration_request_valid_expects_parsed_response(): "type": "RTC", "appId": "YOUR_Voice_appId" } - assert request.callback_url == "https://www.your-callback-server.com/callback" + assert request.event_destination_target == "https://www.your-callback-server.com/callback" def test_update_number_configuration_request_missing_phone_number_expects_error(): @@ -71,4 +71,4 @@ def test_update_number_configuration_request_optional_fields(): assert request.display_name is None assert request.sms_configuration is None assert request.voice_configuration is None - assert request.callback_url is None + assert request.event_destination_target is None diff --git a/tests/unit/domains/numbers/v1/models/internal/test_update_callback_config_request_model.py b/tests/unit/domains/numbers/v1/models/internal/test_update_event_destination_request_model.py similarity index 76% rename from tests/unit/domains/numbers/v1/models/internal/test_update_callback_config_request_model.py rename to tests/unit/domains/numbers/v1/models/internal/test_update_event_destination_request_model.py index 1344c31a..4d1b2820 100644 --- a/tests/unit/domains/numbers/v1/models/internal/test_update_callback_config_request_model.py +++ b/tests/unit/domains/numbers/v1/models/internal/test_update_event_destination_request_model.py @@ -1,6 +1,6 @@ import pytest from pydantic import ValidationError -from sinch.domains.numbers.models.v1.internal import UpdateCallbackConfigurationRequest +from sinch.domains.numbers.models.v1.internal import UpdateEventDestinationRequest def test_update_numbers_callback_config_request_expects_parsed_input(): @@ -10,7 +10,7 @@ def test_update_numbers_callback_config_request_expects_parsed_input(): data = { "hmacSecret": "test-secret-key" } - request = UpdateCallbackConfigurationRequest(**data) + request = UpdateEventDestinationRequest(**data) assert request.hmac_secret == "test-secret-key" @@ -21,7 +21,7 @@ def test_update_numbers_callback_request_expects_validation_for_extra_type(): data = { "extra": "Extra Value" } - request = UpdateCallbackConfigurationRequest(**data) + request = UpdateEventDestinationRequest(**data) assert request.extra == "Extra Value" @@ -30,7 +30,7 @@ def test_update_numbers_callback_config_request_expects_optional_field_handled() Test that hmac_secret is optional and can be None. """ data = {} - request = UpdateCallbackConfigurationRequest(**data) + request = UpdateEventDestinationRequest(**data) assert request.hmac_secret is None @@ -42,4 +42,4 @@ def test_update_numbers_callback_config_request_expects_validation_error(): "hmacSecret": 12345 } with pytest.raises(ValidationError): - UpdateCallbackConfigurationRequest(**data) + UpdateEventDestinationRequest(**data) diff --git a/tests/unit/domains/numbers/v1/models/response/test_active_number_model.py b/tests/unit/domains/numbers/v1/models/response/test_active_number_model.py index e52066bb..1429fb44 100644 --- a/tests/unit/domains/numbers/v1/models/response/test_active_number_model.py +++ b/tests/unit/domains/numbers/v1/models/response/test_active_number_model.py @@ -98,7 +98,7 @@ def test_active_number_response_expects_all_fields_mapped_correctly(test_data): expected_expire_at = ( datetime(2025, 2, 4, 13, 15, 31, 95000, tzinfo=timezone.utc)) assert response.expire_at == expected_expire_at - assert response.callback_url == "https://www.your-callback-server.com/callback" + assert response.event_destination_target == "https://www.your-callback-server.com/callback" assert_sms_configuration(response.sms_configuration) assert_voice_configuration(response.voice_configuration) diff --git a/tests/unit/domains/numbers/v1/models/response/test_numbers_callback_model.py b/tests/unit/domains/numbers/v1/models/response/test_event_destination_response_model.py similarity index 81% rename from tests/unit/domains/numbers/v1/models/response/test_numbers_callback_model.py rename to tests/unit/domains/numbers/v1/models/response/test_event_destination_response_model.py index 1caf4baa..f568e549 100644 --- a/tests/unit/domains/numbers/v1/models/response/test_numbers_callback_model.py +++ b/tests/unit/domains/numbers/v1/models/response/test_event_destination_response_model.py @@ -1,5 +1,5 @@ import pytest -from sinch.domains.numbers.models.v1.response import CallbackConfigurationResponse +from sinch.domains.numbers.models.v1.response import EventDestinationResponse @pytest.fixture @@ -17,7 +17,7 @@ def test_numbers_callback_config_response_all_fields(test_data): Expects all fields to map correctly from camelCase input and handle extra fields appropriately """ - response = CallbackConfigurationResponse(**test_data) + response = EventDestinationResponse(**test_data) assert response.project_id == "project-test-id" assert response.hmac_secret == "secret-key-456" diff --git a/tests/unit/domains/numbers/v1/test_numbers_callback.py b/tests/unit/domains/numbers/v1/test_event_destinations.py similarity index 62% rename from tests/unit/domains/numbers/v1/test_numbers_callback.py rename to tests/unit/domains/numbers/v1/test_event_destinations.py index cf818696..a9d55ff9 100644 --- a/tests/unit/domains/numbers/v1/test_numbers_callback.py +++ b/tests/unit/domains/numbers/v1/test_event_destinations.py @@ -1,11 +1,11 @@ import pytest -from sinch.domains.numbers.api.v1 import CallbackConfiguration +from sinch.domains.numbers.api.v1 import EventDestinations from sinch.domains.numbers.api.v1.internal import ( - GetCallbackConfigurationEndpoint, UpdateCallbackConfigurationEndpoint + GetEventDestinationEndpoint, UpdateEventDestinationEndpoint ) -from sinch.domains.numbers.models.v1.internal import UpdateCallbackConfigurationRequest +from sinch.domains.numbers.models.v1.internal import UpdateEventDestinationRequest from sinch.domains.numbers.models.v1.internal.base import BaseModelConfigurationRequest -from sinch.domains.numbers.models.v1.response import CallbackConfigurationResponse +from sinch.domains.numbers.models.v1.response import EventDestinationResponse @pytest.mark.parametrize( @@ -27,12 +27,12 @@ def test_get_numbers_callback_config_expects_valid_request( Test that the get() method sends the correct request and handles the response properly with or without extra parameters. """ - mock_response = CallbackConfigurationResponse(project_id="test_project_id", hmac_secret="test_secret") + mock_response = EventDestinationResponse(project_id="test_project_id", hmac_secret="test_secret") mock_sinch_client_numbers.configuration.transport.request.return_value = mock_response - spy_endpoint = mocker.spy(GetCallbackConfigurationEndpoint, "__init__") + spy_endpoint = mocker.spy(GetEventDestinationEndpoint, "__init__") - callback_configuration = CallbackConfiguration(mock_sinch_client_numbers) - response = callback_configuration.get(**config_kwargs) + event_destination = EventDestinations(mock_sinch_client_numbers) + response = event_destination.get(**config_kwargs) spy_endpoint.assert_called_once() _, kwargs = spy_endpoint.call_args @@ -50,19 +50,19 @@ def test_update_numbers_callback_config_expects_valid_request(mock_sinch_client_ Test that the update() method sends the correct request and handles the response properly. """ - mock_response = CallbackConfigurationResponse(project_id="test_project_id", hmac_secret="new_secret") + mock_response = EventDestinationResponse(project_id="test_project_id", hmac_secret="new_secret") mock_sinch_client_numbers.configuration.transport.request.return_value = mock_response - spy_endpoint = mocker.spy(UpdateCallbackConfigurationEndpoint, "__init__") + spy_endpoint = mocker.spy(UpdateEventDestinationEndpoint, "__init__") - callback_configuration = CallbackConfiguration(mock_sinch_client_numbers) - response = callback_configuration.update(hmac_secret="new_secret") + event_destination = EventDestinations(mock_sinch_client_numbers) + response = event_destination.update(hmac_secret="new_secret") spy_endpoint.assert_called_once() _, kwargs = spy_endpoint.call_args assert kwargs["project_id"] == "test_project_id" - assert kwargs["request_data"] == UpdateCallbackConfigurationRequest(hmac_secret="new_secret") + assert kwargs["request_data"] == UpdateEventDestinationRequest(hmac_secret="new_secret") assert response == mock_response mock_sinch_client_numbers.configuration.transport.request.assert_called_once() diff --git a/tests/unit/domains/numbers/v1/webhooks/events/test_numbers_webhooks_event_model.py b/tests/unit/domains/numbers/v1/webhooks/events/test_numbers_webhooks_event_model.py index 8ec761ef..44419896 100644 --- a/tests/unit/domains/numbers/v1/webhooks/events/test_numbers_webhooks_event_model.py +++ b/tests/unit/domains/numbers/v1/webhooks/events/test_numbers_webhooks_event_model.py @@ -1,7 +1,7 @@ import pytest from datetime import datetime, timezone from pydantic import ValidationError -from sinch.domains.numbers.webhooks.v1.events import NumbersWebhooksEvent +from sinch.domains.numbers.sinch_events.v1.events import NumberSinchEvent @pytest.fixture @@ -35,7 +35,7 @@ def test_numbers_webhooks_response_expects_parsed_data(valid_data): Expects all fields to map correctly from camelCase input and handle valid data appropriately. """ - response = NumbersWebhooksEvent(**valid_data) + response = NumberSinchEvent(**valid_data) assert response.event_id == "event-123" assert response.timestamp == datetime( @@ -59,7 +59,7 @@ def test_numbers_webhooks_response_missing_optional_fields_expects_parsed_data() "eventId": "event-123", "projectId": "project-456" } - response = NumbersWebhooksEvent(**data) + response = NumberSinchEvent(**data) assert response.event_id == "event-123" assert response.project_id == "project-456" @@ -76,4 +76,4 @@ def test_numbers_webhooks_response_invalid_data_expects_validation_error(invalid Expects the model to raise a validation error for invalid data. """ with pytest.raises(ValidationError): - NumbersWebhooksEvent(**invalid_data) + NumberSinchEvent(**invalid_data) diff --git a/tests/unit/domains/numbers/v1/webhooks/test_numbers_webhooks.py b/tests/unit/domains/numbers/v1/webhooks/test_numbers_webhooks.py index 171a22fc..b157cdb1 100644 --- a/tests/unit/domains/numbers/v1/webhooks/test_numbers_webhooks.py +++ b/tests/unit/domains/numbers/v1/webhooks/test_numbers_webhooks.py @@ -1,7 +1,7 @@ from datetime import datetime, timezone import pytest -from sinch.domains.numbers.webhooks.v1 import NumbersWebhooks -from sinch.domains.numbers.webhooks.v1.events import NumbersWebhooksEvent +from sinch.domains.numbers.sinch_events.v1 import SinchEvents +from sinch.domains.numbers.sinch_events.v1.events import NumberSinchEvent @pytest.fixture @@ -16,7 +16,7 @@ def string_to_sign(): @pytest.fixture def numbers_webhooks(): - return NumbersWebhooks('my-callback-secret') + return SinchEvents('my-callback-secret') @pytest.fixture @@ -67,7 +67,7 @@ def test_parse_event_expects_timestamp_as_utc(numbers_webhooks, test_name, times def test_parse_event_expects_parsed_response(numbers_webhooks, base_payload_parse_event): response = numbers_webhooks.parse_event(base_payload_parse_event) - assert isinstance(response, NumbersWebhooksEvent) + assert isinstance(response, NumberSinchEvent) assert response.event_id == "01jr7stexp0znky34pj07dwp41" assert response.project_id == "project-id" assert response.resource_id == "+1234567890"