diff --git a/README.md b/README.md index 7d72bfc8..9d3143c0 100644 --- a/README.md +++ b/README.md @@ -48,26 +48,54 @@ The Sinch client provides access to the following Sinch products: ### Client initialization +To establish a connection with the Sinch backend, you must provide credentials based on the API you intend to use. +For security best practices, avoid hardcoding credentials — retrieve them from environment variables instead. + +> **Note:** `sms_region` and `conversation_region` no longer have defaults and **must** be set before +> calling those APIs—omitting them will cause a runtime error. See [MIGRATION_GUIDE.md](MIGRATION_GUIDE.md) for details. -To establish a connection with the Sinch backend, you must provide the appropriate credentials based on the API -you intend to use. For security best practices, avoid hardcoding credentials. -Instead, retrieve them from environment variables. #### SMS API -For the SMS API in **Australia (AU)**, **Brazil (BR)**, **Canada (CA)**, **the United States (US)**, -and **the European Union (EU)**, provide the following parameters: + +The SMS API supports two authentication methods. `sms_region` is required for both and has no default. + +**Project auth (OAuth2)** + +The SDK automatically exchanges your key ID and key secret for a short-lived OAuth2 token and refreshes it automatically on expiry. +Supported regions: `us`, `eu`, `br`. + +```python +from sinch import SinchClient + +sinch_client = SinchClient( + project_id="project_id", + key_id="key_id", + key_secret="key_secret", + sms_region="us" +) +``` + +**Service Plan ID auth (legacy)** + +Uses a static bearer token that never expires. +Support all regions: `us`, `eu`, `br`, `ca`, `au`. ```python from sinch import SinchClient sinch_client = SinchClient( service_plan_id="service_plan_id", - sms_api_token="api_token" + sms_api_token="api_token", + sms_region="us" ) ``` -#### All Other Sinch APIs -For all other Sinch APIs, including SMS in US and EU regions, use the following parameters: +#### Conversation API - Project auth (OAuth2) + +`conversation_region` is required and has no default. +Supported regions: `us`, `eu`, `br`. + +> **Why region matters:** The Conversation API stores and routes data within the selected region for regulatory compliance. Choose the region that matches your data residency requirements. ```python from sinch import SinchClient @@ -75,13 +103,26 @@ from sinch import SinchClient sinch_client = SinchClient( project_id="project_id", key_id="key_id", - key_secret="key_secret" + key_secret="key_secret", + conversation_region="eu" ) ``` -### SMS and Conversation regions (V2) +> **SMS integration note:** If you also use the SMS API, `sms_region` and `conversation_region` **must match**. Mismatched regions will cause delivery failures. + +#### Other APIs - Project auth (OAuth2) -You must set `sms_region` before using the SMS API and `conversation_region` before using the Conversation API—either in the `SinchClient(...)` constructor or on `sinch_client.configuration` before the first call to that product. See [MIGRATION_GUIDE.md](MIGRATION_GUIDE.md) for examples. +These APIs are not regionalized and use project-based auth. + +```python +from sinch import SinchClient + +sinch_client = SinchClient( + project_id="project_id", + key_id="key_id", + key_secret="key_secret", +) +``` ## Logging @@ -136,22 +177,62 @@ For handling all possible exceptions thrown by this SDK use `SinchException` (su By default, the HTTP implementation uses the `requests` library. -To use a custom HTTP client, inject your own transport during initialization: +To use a custom HTTP client, assign your transport to the client's configuration after initialization. + +Custom transports must extend `HTTPTransport` and implement the `send` method. The base class provides `prepare_request` and `authenticate` helpers, and handles OAuth token refresh automatically. + +The following example replaces the default `requests` backend with `httpx` and routes traffic through an authenticated proxy: + ```python +import httpx +from sinch import SinchClient +from sinch.core.ports.http_transport import HTTPTransport +from sinch.core.endpoint import HTTPEndpoint +from sinch.core.models.http_response import HTTPResponse + + +class MyHTTPImplementation(HTTPTransport): + def __init__(self, sinch, proxy_url, proxy_user, proxy_password): + super().__init__(sinch) + self.http_client = httpx.Client( + proxy=f"http://{proxy_user}:{proxy_password}@{proxy_url}" + ) + + def send(self, endpoint: HTTPEndpoint) -> HTTPResponse: + request_data = self.prepare_request(endpoint) + request_data = self.authenticate(endpoint, request_data) + + body = request_data.request_body + response = self.http_client.request( + method=request_data.http_method, + url=request_data.url, + json=body if isinstance(body, dict) else None, + content=body if not isinstance(body, dict) else None, + auth=request_data.auth, + headers=request_data.headers, + params=request_data.query_params, + timeout=self.sinch.configuration.connection_timeout, + ) + response_body = self.deserialize_json_response(response) + + return HTTPResponse( + status_code=response.status_code, + body=response_body, + headers=dict(response.headers), + ) + + sinch_client = SinchClient( key_id="key_id", key_secret="key_secret", project_id="some_project", - transport=MyHTTPImplementation ) -``` - -Custom client has to obey types and methods described by `HTTPTransport` abstract base class: -```python -class HTTPTransport(ABC): - @abstractmethod - def request(self, endpoint: HTTPEndpoint) -> HTTPResponse: - pass +sinch_client.configuration.transport = MyHTTPImplementation( + sinch_client, + proxy_url="proxy.example.com:8080", + proxy_user="proxy_user", + proxy_password="proxy_password", +) ``` Note: Asynchronous HTTP clients are not supported.