Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 87 additions & 49 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,93 @@ This release removes legacy SDK support.

This guide lists all removed classes and interfaces from V1 and how to migrate to their V2 equivalents.

## Client Initialization

### Overview

In V2, region parameters are required for domain-specific APIs (SMS and Conversation). These parameters must be set explicitly when initializing `SinchClient`, otherwise API calls will fail at runtime. The parameters are exposed directly on `SinchClient` to ensure they are provided.

### SMS Region

**In V1:**
```python
from sinch import SinchClient

# Using Project auth
sinch_client = SinchClient(
project_id="your-project-id",
key_id="your-key-id",
key_secret="your-key-secret",
)
sinch_client.configuration.sms_region = "eu"

# Or using SMS token auth
token_client = SinchClient(
service_plan_id='your-service-plan-id',
sms_api_token='your-sms-api-token'
)
token_client.configuration.sms_region_with_service_plan_id = "eu"
```

**In V2:**
- The `sms_region` no longer defaults to `us`. Set it explicitly before using the SMS API, otherwise calls will fail at runtime. The parameter is now exposed on `SinchClient` (not just the configuration object) to ensure the region is provided. Note that `sms_region` is only required when using the SMS API endpoints.

```python
from sinch import SinchClient

# Using Project auth
sinch_client = SinchClient(
project_id="your-project-id",
key_id="your-key-id",
key_secret="your-key-secret",
sms_region="eu",
)

# Or using SMS token auth
token_client = SinchClient(
service_plan_id="your-service-plan-id",
sms_api_token="your-sms-api-token",
sms_region="us",
)

# Note: The code is backward compatible. The sms_region can still be set through the configuration object,
# but you must ensure this setting is done BEFORE any SMS API call:
sinch_client.configuration.sms_region = "eu"
```

### Conversation Region

**In V1:**
```python
from sinch import SinchClient

sinch_client = SinchClient(
project_id="your-project-id",
key_id="your-key-id",
key_secret="your-key-secret",
)

sinch_client.configuration.conversation_region = "eu"
```

**In V2:**
- The `conversation_region` no longer defaults to `eu`. This parameter is required now when using the Conversation API endpoints. Set it explicitly when initializing `SinchClient`, otherwise calls will fail at runtime. The parameter is exposed on `SinchClient` to ensure the region is provided.

```python
from sinch import SinchClient

sinch_client = SinchClient(
project_id="your-project-id",
key_id="your-key-id",
key_secret="your-key-secret",
conversation_region="eu",
)

# Note: The conversation_region can also be set through the configuration object,
# but you must ensure this setting is done BEFORE any Conversation API call:
sinch_client.configuration.conversation_region = "eu"
```

### [`SMS`](https://github.com/sinch/sinch-sdk-python/tree/main/sinch/domains/sms)

#### Replacement models
Expand Down Expand Up @@ -68,52 +155,3 @@ 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 |

#### SMS client initialization (with region)
In V1:
```python
from sinch import SinchClient

# Using Project auth
sinch_client = SinchClient(
project_id="your-project-id",
key_id="your-key-id",
key_secret="your-key-secret",
)
sinch_client.configuration.sms_region = "eu"

# Or using SMS token auth
token_client = SinchClient(
service_plan_id='your-service-plan-id',
sms_api_token='your-sms-api-token'
)
token_client.configuration.sms_region_with_service_plan_id = "eu"

```


In V2:
- The `sms_region` no longer defaults to `us`. Set it explicitly before using the SMS API, otherwise calls will fail at runtime. The parameter is now exposed on `SinchClient` (not just the configuration object) to ensure the region is provided. Note that `sms_region` is only required when using the SMS API endpoints.

```python
from sinch import SinchClient

# Using Project auth
sinch_client = SinchClient(
project_id="your-project-id",
key_id="your-key-id",
key_secret="your-key-secret",
sms_region="eu",
)

# or using SMS token auth
token_client = SinchClient(
service_plan_id="your-service-plan-id",
sms_api_token="your-sms-api-token",
sms_region="us",
)

# Note: The code is backward compatible. The sms_region can still be set through the configuration object,
# but you must ensure this setting is done BEFORE any SMS API call:
sinch_client.configuration.sms_region = "eu"
```
27 changes: 24 additions & 3 deletions sinch/core/clients/sinch_client_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def __init__(
service_plan_id: str = None,
sms_api_token: str = None,
sms_region: str = None,
conversation_region: str = None,
):
self.key_id = key_id
self.key_secret = key_secret
Expand All @@ -44,8 +45,8 @@ def __init__(
self.number_lookup_origin = "https://lookup.api.sinch.com"
self._voice_domain = "https://{}.api.sinch.com"
self._voice_region = None
self._conversation_region = "eu"
self._conversation_domain = ".conversation.api.sinch.com"
self._conversation_region = conversation_region
self._conversation_domain = "https://{}.conversation.api.sinch.com/"
self._sms_region = sms_region
self._sms_region_with_service_plan_id = sms_region
self._sms_domain = "https://zt.{}.sms.api.sinch.com"
Expand Down Expand Up @@ -135,7 +136,10 @@ def _get_sms_domain(self):
)

def _set_conversation_origin(self):
self.conversation_origin = self._conversation_region + self._conversation_domain
if self._conversation_region:
self.conversation_origin = self._conversation_domain.format(self._conversation_region)
else:
self.conversation_origin = None

def _set_conversation_region(self, region):
self._conversation_region = region
Expand Down Expand Up @@ -284,3 +288,20 @@ def get_sms_origin_for_auth(self):
)

return origin

def get_conversation_origin(self):
"""
Returns the conversation origin.

Raises:
ValueError: If the conversation region is None (conversation_region not set)
"""
if self.conversation_origin is None:
raise ValueError(
"Conversation region is required. "
"Provide conversation_region when initializing SinchClient "
"Example: SinchClient(project_id='...', key_id='...', key_secret='...', conversation_region='eu')"
"or set it via sinch_client.configuration.conversation_region. "
)

return self.conversation_origin
2 changes: 2 additions & 0 deletions sinch/core/clients/sinch_client_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def __init__(
service_plan_id: str = None,
sms_api_token: str = None,
sms_region: str = None,
conversation_region: str = None,
):
self.configuration = Configuration(
key_id=key_id,
Expand All @@ -43,6 +44,7 @@ def __init__(
service_plan_id=service_plan_id,
sms_api_token=sms_api_token,
sms_region=sms_region,
conversation_region=conversation_region,
)

self.authentication = Authentication(self)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ def build_url(self, sinch) -> str:
f"'{self.__class__.__name__}'."
)

# TODO: Add support and validation for conversation_region in SinchClient initialization;
origin = sinch.configuration.get_conversation_origin()

return self.ENDPOINT_URL.format(
origin=sinch.configuration.conversation_origin,
origin=origin,
project_id=self.project_id,
**vars(self.request_data),
)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime
from typing import List, Optional
from pydantic import Field, StrictStr, StrictInt
from typing import Optional
from pydantic import Field, StrictStr, StrictInt, conlist
from sinch.domains.conversation.models.v1.messages.categories.channelspecific.whatsapp.payment.order_item import (
OrderItem,
)
Expand All @@ -10,7 +10,7 @@


class PaymentOrder(BaseModelConfigurationResponse):
items: List[OrderItem] = Field(
items: conlist(OrderItem) = Field(
..., description="The items list for this order."
)
subtotal_value: StrictInt = Field(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
ProductItem,
)
from sinch.domains.conversation.models.v1.messages.shared.reason import Reason
from sinch.domains.conversation.models.v1.messages.shared.reason_sub_code import (
ReasonSubCode,
)

__all__ = [
"AddressInfo",
Expand All @@ -41,7 +38,6 @@
"OmniMessageOverride",
"ProductItem",
"Reason",
"ReasonSubCode",
]


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from sinch.domains.conversation.models.v1.messages.types.reason_code_type import (
ReasonCodeType,
)
from sinch.domains.conversation.models.v1.messages.shared.reason_sub_code import (
ReasonSubCode,
from sinch.domains.conversation.models.v1.messages.types.reason_sub_code_type import (
ReasonSubCodeType,
)
from sinch.domains.conversation.models.v1.messages.internal.base import (
BaseModelConfigurationResponse,
Expand All @@ -16,7 +16,7 @@ class Reason(BaseModelConfigurationResponse):
description: Optional[StrictStr] = Field(
default=None, description="A textual description of the reason."
)
sub_code: Optional[ReasonSubCode] = None
sub_code: Optional[ReasonSubCodeType] = None
channel_code: Optional[StrictStr] = Field(
default=None,
description="Error code forwarded directly from the channel. Useful in case of unmapped or channel specific errors. Currently only supported on the WhatsApp channel.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
from sinch.domains.conversation.models.v1.messages.types.reason_code_type import (
ReasonCodeType,
)
from sinch.domains.conversation.models.v1.messages.types.reason_sub_code_type import (
ReasonSubCodeType,
)
from sinch.domains.conversation.models.v1.messages.types.whatsapp_interactive_nfm_reply_name_type import (
WhatsAppInteractiveNfmReplyNameType,
)
Expand All @@ -51,5 +54,6 @@
"PaymentOrderType",
"PixKeyType",
"ReasonCodeType",
"ReasonSubCodeType",
"WhatsAppInteractiveNfmReplyNameType",
]
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from pydantic import StrictStr


ReasonSubCode = Union[
ReasonSubCodeType = Union[
Literal[
"UNSPECIFIED_SUB_CODE",
"ATTACHMENT_REJECTED",
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,30 @@ def test_sinch_client_expects_all_attributes():
assert hasattr(sinch_client, "voice")
assert hasattr(sinch_client, "configuration")
assert isinstance(sinch_client.configuration, Configuration)


def test_sinch_client_expects_to_be_initialized_with_conversation_region():
""" Test that SinchClient can be initialized with conversation_region """
sinch_client = SinchClient(
key_id="test_key_id",
key_secret="test_key_secret",
project_id="test_project_id",
conversation_region="eu"
)
assert sinch_client.configuration.conversation_region == "eu"
assert sinch_client.configuration.conversation_origin == "https://eu.conversation.api.sinch.com/"


def test_sinch_client_expects_conversation_region_error_when_not_provided():
""" Test that get_conversation_origin raises ValueError when SinchClient is initialized without conversation_region """
sinch_client = SinchClient(
key_id="test_key_id",
key_secret="test_key_secret",
project_id="test_project_id"
)

assert sinch_client.configuration.conversation_region is None
assert sinch_client.configuration.conversation_origin is None

with pytest.raises(ValueError, match="Conversation region is required"):
sinch_client.configuration.get_conversation_origin()
Loading