diff --git a/sdk/batch/speechmatics/batch/_async_client.py b/sdk/batch/speechmatics/batch/_async_client.py index 873218b..27ba3f6 100644 --- a/sdk/batch/speechmatics/batch/_async_client.py +++ b/sdk/batch/speechmatics/batch/_async_client.py @@ -9,7 +9,6 @@ import asyncio import os -import uuid from typing import Any from typing import BinaryIO from typing import Optional @@ -101,11 +100,10 @@ def __init__( self._auth = auth or StaticKeyAuth(api_key) self._url = url or os.environ.get("SPEECHMATICS_BATCH_URL") or "https://asr.api.speechmatics.com/v2" 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/batch/speechmatics/batch/_transport.py b/sdk/batch/speechmatics/batch/_transport.py index 4beb3d6..7f8b542 100644 --- a/sdk/batch/speechmatics/batch/_transport.py +++ b/sdk/batch/speechmatics/batch/_transport.py @@ -41,12 +41,9 @@ 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: @@ -63,7 +60,6 @@ def __init__( url: str, conn_config: ConnectionConfig, auth: AuthBase, - request_id: Optional[str] = None, ) -> None: """ Initialize the transport with connection configuration. @@ -71,18 +67,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.""" @@ -239,15 +232,17 @@ 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) if extra_headers: for k, v in extra_headers.items(): headers[k] = _json.dumps(v) if isinstance(v, dict) else v 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, ) @@ -291,21 +286,25 @@ async def _request( kwargs["data"] = form_data async with self._session.request(method, url, **kwargs) as response: - return await self._handle_response(response) + return await self._handle_response(response, request_id) 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. @@ -313,21 +312,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) -> dict[str, Any]: + async def _handle_response(self, response: aiohttp.ClientResponse, request_id: str) -> dict[str, Any]: """ Handle HTTP response and extract JSON data. Args: response: HTTP response object + request_id: Request ID for correlation in logs and error messages Returns: JSON response as dictionary @@ -338,13 +336,17 @@ async def _handle_response(self, response: aiohttp.ClientResponse) -> dict[str, """ 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})" + ) # Try to parse JSON response if ( @@ -354,13 +356,15 @@ async def _handle_response(self, response: aiohttp.ClientResponse) -> dict[str, return await response.json() # type: ignore[no-any-return] else: # For non-JSON responses (like plain text transcripts) - self._logger.debug("Parsing text response (content_type=%s)", response.content_type) + self._logger.debug( + "Parsing text response (request_id=%s, content_type=%s)", request_id, response.content_type + ) text = await response.text() return {"content": text, "content_type": response.content_type} 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