From 58772840cc06e8725ff05ff51203c817d36b8845 Mon Sep 17 00:00:00 2001 From: Dumitru Gutu Date: Mon, 11 May 2026 09:27:51 +0100 Subject: [PATCH 1/2] Fix tts sdk re-using requestid across multiple jobs --- sdk/tts/speechmatics/tts/_async_client.py | 6 +- sdk/tts/speechmatics/tts/_transport.py | 72 ++++++++++++----------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/sdk/tts/speechmatics/tts/_async_client.py b/sdk/tts/speechmatics/tts/_async_client.py index ab200b57..6c1242d2 100644 --- a/sdk/tts/speechmatics/tts/_async_client.py +++ b/sdk/tts/speechmatics/tts/_async_client.py @@ -8,7 +8,6 @@ from __future__ import annotations import os -import uuid from typing import Any from typing import Optional @@ -88,11 +87,10 @@ def __init__( self._auth = auth or StaticKeyAuth(api_key) self._url = url or os.environ.get("SPEECHMATICS_TTS_URL") or "https://preview.tts.speechmatics.com" self._conn_config = conn_config or ConnectionConfig() - self._request_id = str(uuid.uuid4()) - self._transport = Transport(self._url, self._conn_config, self._auth, self._request_id) + self._transport = Transport(self._url, self._conn_config, self._auth) self._logger = get_logger(__name__) - self._logger.debug("AsyncClient initialized (request_id=%s, url=%s)", self._request_id, self._url) + self._logger.debug("AsyncClient initialized (url=%s)", self._url) async def __aenter__(self) -> AsyncClient: """ diff --git a/sdk/tts/speechmatics/tts/_transport.py b/sdk/tts/speechmatics/tts/_transport.py index 1377be06..9c8d76b9 100644 --- a/sdk/tts/speechmatics/tts/_transport.py +++ b/sdk/tts/speechmatics/tts/_transport.py @@ -38,20 +38,17 @@ class Transport: url: Base URL for the Speechmatics Batch API. conn_config: Connection configuration including URL and timeouts. auth: Authentication instance for handling credentials. - request_id: Optional unique identifier for request tracking. Generated - automatically if not provided. Attributes: conn_config: The connection configuration object. - request_id: Unique identifier for this transport instance. Examples: Basic usage: >>> from ._auth import StaticKeyAuth >>> conn_config = ConnectionConfig() >>> auth = StaticKeyAuth("your-api-key") - >>> transport = Transport(conn_config, auth) - >>> response = await transport.get("/jobs") + >>> transport = Transport("https://preview.tts.speechmatics.com", conn_config, auth) + >>> response = await transport.post("/v1/audio") >>> await transport.close() """ @@ -60,7 +57,6 @@ def __init__( url: str, conn_config: ConnectionConfig, auth: AuthBase, - request_id: Optional[str] = None, ) -> None: """ Initialize the transport with connection configuration. @@ -68,18 +64,15 @@ def __init__( Args: conn_config: Connection configuration object containing connection parameters. auth: Authentication instance for handling credentials. - request_id: Optional unique identifier for request tracking. - Generated automatically if not provided. """ self._url = url self._conn_config = conn_config self._auth = auth - self._request_id = request_id or str(uuid.uuid4()) self._session: Optional[aiohttp.ClientSession] = None self._closed = False self._logger = get_logger(__name__) - self._logger.debug("Transport initialized (request_id=%s, url=%s)", self._request_id, self._url) + self._logger.debug("Transport initialized (url=%s)", self._url) async def __aenter__(self) -> Transport: """Async context manager entry.""" @@ -189,12 +182,14 @@ async def _request( raise ConnectionError("Failed to create HTTP session") url = f"{self._url.rstrip('/')}{path}" - headers = await self._prepare_headers() + request_id = str(uuid.uuid4()) + headers = await self._prepare_headers(request_id) self._logger.debug( - "Sending HTTP request %s %s (json=%s, multipart=%s)", + "Sending HTTP request %s %s (request_id=%s, json=%s, multipart=%s)", method, url, + request_id, json_data is not None, multipart_data is not None, ) @@ -239,24 +234,28 @@ async def _request( response = await self._session.request(method, url, **kwargs) try: - return await self._handle_response(response) + return await self._handle_response(response, request_id) except Exception: response.close() raise except asyncio.TimeoutError: self._logger.error( - "Request timeout %s %s (timeout=%.1fs)", method, path, self._conn_config.operation_timeout + "Request timeout %s %s (request_id=%s, timeout=%.1fs)", + method, + path, + request_id, + self._conn_config.operation_timeout, ) - raise TransportError(f"Request timeout for {method} {path}") from None + raise TransportError(f"Request timeout for {method} {path} (request_id={request_id})") from None except aiohttp.ClientError as e: - self._logger.error("Request failed %s %s: %s", method, path, e) - raise ConnectionError(f"Request failed: {e}") from e + self._logger.error("Request failed %s %s (request_id=%s): %s", method, path, request_id, e) + raise ConnectionError(f"Request failed (request_id={request_id}): {e}") from e except Exception as e: - self._logger.error("Unexpected error %s %s: %s", method, path, e) - raise TransportError(f"Unexpected error: {e}") from e + self._logger.error("Unexpected error %s %s (request_id=%s): %s", method, path, request_id, e) + raise TransportError(f"Unexpected error (request_id={request_id}): {e}") from e - async def _prepare_headers(self) -> dict[str, str]: + async def _prepare_headers(self, request_id: str) -> dict[str, str]: """ Prepare HTTP headers for requests. @@ -264,21 +263,20 @@ async def _prepare_headers(self) -> dict[str, str]: Headers dictionary with authentication and tracking info """ auth_headers = await self._auth.get_auth_headers() - auth_headers["User-Agent"] = ( - f"speechmatics-batch-v{get_version()} python/{sys.version_info.major}.{sys.version_info.minor}" - ) - - if self._request_id: - auth_headers["X-Request-Id"] = self._request_id + auth_headers[ + "User-Agent" + ] = f"speechmatics-batch-v{get_version()} python/{sys.version_info.major}.{sys.version_info.minor}" + auth_headers["X-Request-Id"] = request_id return auth_headers - async def _handle_response(self, response: aiohttp.ClientResponse) -> aiohttp.ClientResponse: + async def _handle_response(self, response: aiohttp.ClientResponse, request_id: str) -> aiohttp.ClientResponse: """ Handle HTTP response and extract JSON data. Args: response: HTTP response object + request_id: Request ID for correlation in logs and error messages Returns: HTTP response object @@ -289,18 +287,22 @@ async def _handle_response(self, response: aiohttp.ClientResponse) -> aiohttp.Cl """ try: if response.status == 401: - raise AuthenticationError("Invalid API key - authentication failed") + raise AuthenticationError(f"Invalid API key - authentication failed (request_id={request_id})") elif response.status == 403: - raise AuthenticationError("Access forbidden - check API key permissions") + raise AuthenticationError(f"Access forbidden - check API key permissions (request_id={request_id})") elif response.status >= 400: error_text = await response.text() - self._logger.error("HTTP error %d %s: %s", response.status, response.reason, error_text) - raise TransportError(f"HTTP {response.status}: {response.reason} - {error_text}") + self._logger.error( + "HTTP error %d %s (request_id=%s): %s", response.status, response.reason, request_id, error_text + ) + raise TransportError( + f"HTTP {response.status}: {response.reason} - {error_text} (request_id={request_id})" + ) return response except aiohttp.ContentTypeError as e: - self._logger.error("Failed to parse JSON response: %s", e) - raise TransportError(f"Failed to parse response: {e}") from e + self._logger.error("Failed to parse JSON response (request_id=%s): %s", request_id, e) + raise TransportError(f"Failed to parse response (request_id={request_id}): {e}") from e except Exception as e: - self._logger.error("Error handling response: %s", e) - raise TransportError(f"Error handling response: {e}") from e + self._logger.error("Error handling response (request_id=%s): %s", request_id, e) + raise TransportError(f"Error handling response (request_id={request_id}): {e}") from e From d789cc48aa091085279a97ebdb6187ce39c34f4b Mon Sep 17 00:00:00 2001 From: Dumitru Gutu Date: Mon, 11 May 2026 11:01:29 +0100 Subject: [PATCH 2/2] Fix tts sdk re-using requestid across multiple jobs --- sdk/tts/speechmatics/tts/_transport.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/tts/speechmatics/tts/_transport.py b/sdk/tts/speechmatics/tts/_transport.py index 9c8d76b9..fc719b94 100644 --- a/sdk/tts/speechmatics/tts/_transport.py +++ b/sdk/tts/speechmatics/tts/_transport.py @@ -47,8 +47,8 @@ class Transport: >>> from ._auth import StaticKeyAuth >>> conn_config = ConnectionConfig() >>> auth = StaticKeyAuth("your-api-key") - >>> transport = Transport("https://preview.tts.speechmatics.com", conn_config, auth) - >>> response = await transport.post("/v1/audio") + >>> transport = Transport(conn_config, auth) + >>> response = await transport.generate() >>> await transport.close() """