airia 0.1.13__py3-none-any.whl → 0.1.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. airia/client/_request_handler/__init__.py +4 -0
  2. airia/client/_request_handler/async_request_handler.py +272 -0
  3. airia/client/_request_handler/base_request_handler.py +108 -0
  4. airia/client/_request_handler/sync_request_handler.py +255 -0
  5. airia/client/async_client.py +25 -678
  6. airia/client/base_client.py +2 -368
  7. airia/client/conversations/__init__.py +4 -0
  8. airia/client/conversations/async_conversations.py +187 -0
  9. airia/client/conversations/base_conversations.py +135 -0
  10. airia/client/conversations/sync_conversations.py +182 -0
  11. airia/client/pipeline_execution/__init__.py +4 -0
  12. airia/client/pipeline_execution/async_pipeline_execution.py +178 -0
  13. airia/client/pipeline_execution/base_pipeline_execution.py +96 -0
  14. airia/client/pipeline_execution/sync_pipeline_execution.py +178 -0
  15. airia/client/pipelines_config/__init__.py +4 -0
  16. airia/client/pipelines_config/async_pipelines_config.py +127 -0
  17. airia/client/pipelines_config/base_pipelines_config.py +76 -0
  18. airia/client/pipelines_config/sync_pipelines_config.py +127 -0
  19. airia/client/project/__init__.py +4 -0
  20. airia/client/project/async_project.py +122 -0
  21. airia/client/project/base_project.py +74 -0
  22. airia/client/project/sync_project.py +120 -0
  23. airia/client/store/__init__.py +4 -0
  24. airia/client/store/async_store.py +377 -0
  25. airia/client/store/base_store.py +243 -0
  26. airia/client/store/sync_store.py +352 -0
  27. airia/client/sync_client.py +25 -656
  28. airia/constants.py +1 -1
  29. airia/exceptions.py +8 -8
  30. airia/logs.py +9 -9
  31. airia/types/_request_data.py +11 -4
  32. airia/types/api/__init__.py +0 -27
  33. airia/types/api/conversations/__init__.py +3 -0
  34. airia/types/api/{conversations.py → conversations/_conversations.py} +49 -12
  35. airia/types/api/pipeline_execution/__init__.py +13 -0
  36. airia/types/api/{pipeline_execution.py → pipeline_execution/_pipeline_execution.py} +30 -13
  37. airia/types/api/pipelines_config/__init__.py +3 -0
  38. airia/types/api/pipelines_config/get_pipeline_config.py +401 -0
  39. airia/types/api/project/__init__.py +3 -0
  40. airia/types/api/{get_projects.py → project/get_projects.py} +16 -4
  41. airia/types/api/store/__init__.py +4 -0
  42. airia/types/api/store/get_file.py +145 -0
  43. airia/types/api/store/get_files.py +21 -0
  44. airia/types/sse/__init__.py +1 -0
  45. airia/types/sse/sse_messages.py +55 -21
  46. airia/utils/sse_parser.py +5 -4
  47. {airia-0.1.13.dist-info → airia-0.1.14.dist-info}/METADATA +4 -2
  48. airia-0.1.14.dist-info/RECORD +55 -0
  49. airia/types/api/get_pipeline_config.py +0 -214
  50. airia-0.1.13.dist-info/RECORD +0 -24
  51. {airia-0.1.13.dist-info → airia-0.1.14.dist-info}/WHEEL +0 -0
  52. {airia-0.1.13.dist-info → airia-0.1.14.dist-info}/licenses/LICENSE +0 -0
  53. {airia-0.1.13.dist-info → airia-0.1.14.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,4 @@
1
+ from .async_request_handler import AsyncRequestHandler
2
+ from .sync_request_handler import RequestHandler
3
+
4
+ __all__ = ["RequestHandler", "AsyncRequestHandler"]
@@ -0,0 +1,272 @@
1
+ import asyncio
2
+ import weakref
3
+ from typing import Any, AsyncIterator, Dict, Optional
4
+
5
+ import aiohttp
6
+ import loguru
7
+
8
+ from ...exceptions import AiriaAPIError
9
+ from ...types._request_data import RequestData
10
+ from ...utils.sse_parser import async_parse_sse_stream_chunked
11
+ from .base_request_handler import BaseRequestHandler
12
+
13
+
14
+ class AsyncRequestHandler(BaseRequestHandler):
15
+ def __init__(
16
+ self,
17
+ logger: "loguru.Logger",
18
+ timeout: float,
19
+ base_url: str,
20
+ api_key: Optional[str] = None,
21
+ bearer_token: Optional[str] = None,
22
+ log_requests: bool = False,
23
+ ):
24
+ self.session = aiohttp.ClientSession()
25
+
26
+ self._finalizer = weakref.finalize(self, self._cleanup_session, self.session)
27
+
28
+ super().__init__(
29
+ logger=logger,
30
+ timeout=timeout,
31
+ api_key=api_key,
32
+ base_url=base_url,
33
+ bearer_token=bearer_token,
34
+ log_requests=log_requests,
35
+ )
36
+
37
+ @staticmethod
38
+ def _cleanup_session(session: aiohttp.ClientSession):
39
+ """Static method to clean up session - called by finalizer"""
40
+ if session and not session.closed:
41
+ # Create a new event loop if none exists
42
+ try:
43
+ loop = asyncio.get_event_loop()
44
+ if loop.is_closed():
45
+ raise RuntimeError("Event loop is closed")
46
+ except RuntimeError:
47
+ loop = asyncio.new_event_loop()
48
+ asyncio.set_event_loop(loop)
49
+
50
+ # Close the session
51
+ if not loop.is_running():
52
+ loop.run_until_complete(session.close())
53
+ else:
54
+ # If loop is running, schedule the close operation
55
+ asyncio.create_task(session.close())
56
+
57
+ async def close(self):
58
+ """
59
+ Closes the aiohttp session to free up system resources.
60
+
61
+ This method should be called when the RequestHandler is no longer needed to ensure
62
+ proper cleanup of the underlying session and its resources.
63
+ """
64
+ if self.session and not self.session.closed:
65
+ await self.session.close()
66
+
67
+ def _handle_exception(
68
+ self, e: aiohttp.ClientResponseError, url: str, correlation_id: str
69
+ ):
70
+ # Log the error response if enabled
71
+ if self.log_requests:
72
+ self.logger.error(
73
+ f"API Error: {e.status} {e.message}\n"
74
+ f"URL: {url}\n"
75
+ f"Correlation ID: {correlation_id}"
76
+ )
77
+
78
+ # Extract error details from response
79
+ error_message = e.message
80
+
81
+ # Make sure sensitive auth information is not included in error messages
82
+ sanitized_message = error_message
83
+ if self.api_key and self.api_key in sanitized_message:
84
+ sanitized_message = sanitized_message.replace(self.api_key, "[REDACTED]")
85
+ if self.bearer_token and self.bearer_token in sanitized_message:
86
+ sanitized_message = sanitized_message.replace(
87
+ self.bearer_token, "[REDACTED]"
88
+ )
89
+
90
+ # Raise custom exception with status code and sanitized message
91
+ raise AiriaAPIError(status_code=e.status, message=sanitized_message) from e
92
+
93
+ async def make_request(
94
+ self, method: str, request_data: RequestData, return_json: bool = True
95
+ ) -> Optional[Dict[str, Any]]:
96
+ """
97
+ Makes an asynchronous HTTP request to the Airia API.
98
+
99
+ Args:
100
+ method (str): The HTTP method (e.g., 'GET', 'POST')
101
+ request_data: A dictionary containing the following request information:
102
+ - url: The endpoint URL for the request
103
+ - headers: HTTP headers to include in the request
104
+ - payload: The JSON payload/body for the request
105
+ - params: Optional query parameters to append to the URL
106
+ - files: Optional file data to be uploaded in the request body
107
+ - correlation_id: Unique identifier for request tracing
108
+ return_json (bool): Whether to return the response as JSON. Default is True.
109
+
110
+ Returns:
111
+ resp ([Dict[str, Any]): The JSON response from the API as a dictionary.
112
+
113
+ Raises:
114
+ AiriaAPIError: If the API returns an error response, with details about the error
115
+ aiohttp.ClientResponseError: For HTTP-related errors
116
+
117
+ Note:
118
+ This is an internal method used by other client methods to make API requests.
119
+ It handles logging, error handling, and API key redaction in error messages.
120
+ """
121
+ try:
122
+ # Make the request
123
+ async with self.session.request(
124
+ method=method,
125
+ url=request_data.url,
126
+ json=request_data.payload,
127
+ params=request_data.params,
128
+ headers=request_data.headers,
129
+ timeout=self.timeout,
130
+ ) as response:
131
+ # Log the response if enabled
132
+ if self.log_requests:
133
+ self.logger.info(
134
+ f"API Response: {response.status} {response.reason}\n"
135
+ f"URL: {request_data.url}\n"
136
+ f"Correlation ID: {request_data.correlation_id}"
137
+ )
138
+
139
+ # Check for HTTP errors
140
+ response.raise_for_status()
141
+
142
+ # Return the response as a dictionary
143
+ if return_json:
144
+ return await response.json()
145
+
146
+ except aiohttp.ClientResponseError as e:
147
+ self._handle_exception(e, request_data.url, request_data.correlation_id)
148
+
149
+ async def make_request_stream(
150
+ self, method: str, request_data: RequestData
151
+ ) -> AsyncIterator[str]:
152
+ """
153
+ Makes an asynchronous HTTP request to the Airia API.
154
+
155
+ Args:
156
+ method (str): The HTTP method (e.g., 'GET', 'POST')
157
+ request_data: A dictionary containing the following request information:
158
+ - url: The endpoint URL for the request
159
+ - headers: HTTP headers to include in the request
160
+ - payload: The JSON payload/body for the request
161
+ - params: Optional query parameters to append to the URL
162
+ - files: Optional file data to be uploaded in the request body
163
+ - correlation_id: Unique identifier for request tracing
164
+
165
+ Yields:
166
+ resp AsyncIterator[str]]: yields chunks of the response as they are received.
167
+
168
+ Raises:
169
+ AiriaAPIError: If the API returns an error response, with details about the error
170
+ aiohttp.ClientResponseError: For HTTP-related errors
171
+
172
+ Note:
173
+ This is an internal method used by other client methods to make API requests.
174
+ It handles logging, error handling, and API key redaction in error messages.
175
+ """
176
+ try:
177
+ # Make the request
178
+ async with self.session.request(
179
+ method=method,
180
+ url=request_data.url,
181
+ json=request_data.payload,
182
+ params=request_data.params,
183
+ headers=request_data.headers,
184
+ timeout=self.timeout,
185
+ chunked=True,
186
+ ) as response:
187
+ # Log the response if enabled
188
+ if self.log_requests:
189
+ self.logger.info(
190
+ f"API Response: {response.status} {response.reason}\n"
191
+ f"URL: {request_data.url}\n"
192
+ f"Correlation ID: {request_data.correlation_id}"
193
+ )
194
+
195
+ # Check for HTTP errors
196
+ response.raise_for_status()
197
+
198
+ # Yields the response content as a stream if streaming
199
+ async for message in async_parse_sse_stream_chunked(
200
+ response.content.iter_any()
201
+ ):
202
+ yield message
203
+
204
+ except aiohttp.ClientResponseError as e:
205
+ self._handle_exception(e, request_data.url, request_data.correlation_id)
206
+
207
+ async def make_request_multipart(
208
+ self, method: str, request_data: RequestData, return_json: bool = True
209
+ ) -> Optional[Dict[str, Any]]:
210
+ """
211
+ Makes an asynchronous HTTP request with multipart form data to the Airia API.
212
+
213
+ Args:
214
+ method (str): The HTTP method (e.g., 'POST')
215
+ request_data: A dictionary containing the following request information:
216
+ - url: The endpoint URL for the request
217
+ - headers: HTTP headers to include in the request
218
+ - payload: The form data payload including file content
219
+ - params: Optional query parameters to append to the URL
220
+ - files: Optional file data to be uploaded in the request body
221
+ - correlation_id: Unique identifier for request tracing
222
+ return_json (bool): Whether to return the response as JSON. Default is True.
223
+
224
+ Returns:
225
+ resp (Optional[Dict[str, Any]]): The JSON response from the API as a dictionary.
226
+
227
+ Raises:
228
+ AiriaAPIError: If the API returns an error response, with details about the error
229
+ aiohttp.ClientResponseError: For HTTP-related errors
230
+
231
+ Note:
232
+ This is an internal method used by file upload methods to make multipart requests.
233
+ It handles multipart form data encoding, logging, and error handling.
234
+ """
235
+ try:
236
+ # Prepare multipart form data
237
+ data = aiohttp.FormData()
238
+
239
+ # Add form fields
240
+ for key, value in request_data.payload.items():
241
+ data.add_field(key, str(value))
242
+
243
+ # Add files
244
+ for key, value in request_data.files.items():
245
+ data.add_field(key, value[1], filename=value[0], content_type=value[2])
246
+
247
+ # Make the request
248
+ async with self.session.request(
249
+ method=method,
250
+ url=request_data.url,
251
+ data=data,
252
+ params=request_data.params,
253
+ headers=request_data.headers,
254
+ timeout=self.timeout,
255
+ ) as response:
256
+ # Log the response if enabled
257
+ if self.log_requests:
258
+ self.logger.info(
259
+ f"API Response: {response.status} {response.reason}\n"
260
+ f"URL: {request_data.url}\n"
261
+ f"Correlation ID: {request_data.correlation_id}"
262
+ )
263
+
264
+ # Check for HTTP errors
265
+ response.raise_for_status()
266
+
267
+ # Return the response as a dictionary
268
+ if return_json:
269
+ return await response.json()
270
+
271
+ except aiohttp.ClientResponseError as e:
272
+ self._handle_exception(e, request_data.url, request_data.correlation_id)
@@ -0,0 +1,108 @@
1
+ import json
2
+ from typing import Any, Dict, Optional
3
+
4
+ import loguru
5
+
6
+ from ...logs import set_correlation_id
7
+ from ...types._request_data import RequestData
8
+
9
+
10
+ class BaseRequestHandler:
11
+ def __init__(
12
+ self,
13
+ logger: "loguru.Logger",
14
+ timeout: float,
15
+ base_url: str,
16
+ api_key: Optional[str] = None,
17
+ bearer_token: Optional[str] = None,
18
+ log_requests: bool = False,
19
+ ):
20
+ self.logger = logger
21
+ self.timeout = timeout
22
+ self.base_url = base_url
23
+ self.api_key = api_key
24
+ self.bearer_token = bearer_token
25
+ self.log_requests = log_requests
26
+
27
+ def close(self):
28
+ raise NotImplementedError("Subclasses must implement this method")
29
+
30
+ def prepare_request(
31
+ self,
32
+ url: str,
33
+ payload: Optional[Dict[str, Any]] = None,
34
+ params: Optional[Dict[str, Any]] = None,
35
+ files: Optional[Dict[str, Any]] = None,
36
+ correlation_id: Optional[str] = None,
37
+ ):
38
+ """
39
+ Prepare request data including headers, authentication, and logging.
40
+
41
+ This method sets up all the necessary components for an API request including
42
+ correlation ID, authentication headers, and sanitized logging.
43
+
44
+ Args:
45
+ url: The target URL for the request
46
+ payload: Optional JSON payload for the request body
47
+ params: Optional query parameters for the request
48
+ files: Optional files to be uploaded in the request body
49
+ correlation_id: Optional correlation ID for request tracing
50
+
51
+ Returns:
52
+ RequestData: A data structure containing all prepared request components
53
+ """
54
+ # Set correlation ID if provided or generate a new one
55
+ correlation_id = set_correlation_id(correlation_id)
56
+
57
+ # Set up base headers
58
+ headers = {"X-Correlation-ID": correlation_id}
59
+
60
+ # Add authentication header based on the method used
61
+ if self.api_key:
62
+ headers["X-API-KEY"] = self.api_key
63
+ elif self.bearer_token:
64
+ headers["Authorization"] = f"Bearer {self.bearer_token}"
65
+
66
+ # Log the request if enabled
67
+ if self.log_requests:
68
+ # Create a sanitized copy of headers and params for logging
69
+ log_headers = headers.copy()
70
+ log_params = params.copy() if params is not None else {}
71
+ log_files = (
72
+ {k: (v[0], "[BINARY DATA]", v[2]) for k, v in files.items()}
73
+ if files is not None
74
+ else {}
75
+ )
76
+
77
+ # Filter out sensitive headers
78
+ if "X-API-KEY" in log_headers:
79
+ log_headers["X-API-KEY"] = "[REDACTED]"
80
+ if "Authorization" in log_headers:
81
+ log_headers["Authorization"] = "[REDACTED]"
82
+
83
+ # Process payload for logging
84
+ log_payload = payload.copy() if payload is not None else {}
85
+ if "images" in log_payload and log_payload["images"] is not None:
86
+ log_payload["images"] = f"{len(log_payload['images'])} images"
87
+ if "files" in log_payload and log_payload["files"] is not None:
88
+ log_payload["files"] = f"{len(log_payload['files'])} files"
89
+ log_payload = json.dumps(log_payload)
90
+
91
+ self.logger.info(
92
+ f"API Request: POST {url}\n"
93
+ f"Headers: {json.dumps(log_headers)}\n"
94
+ f"Payload: {log_payload}\n"
95
+ f"Files: {log_files}\n"
96
+ f"Params: {json.dumps(log_params)}\n"
97
+ )
98
+
99
+ return RequestData(
100
+ **{
101
+ "url": url,
102
+ "payload": payload,
103
+ "headers": headers,
104
+ "params": params,
105
+ "files": files,
106
+ "correlation_id": correlation_id,
107
+ }
108
+ )
@@ -0,0 +1,255 @@
1
+ import weakref
2
+ from typing import Any, Dict, Optional
3
+
4
+ import loguru
5
+ import requests
6
+
7
+ from ...exceptions import AiriaAPIError
8
+ from ...types._request_data import RequestData
9
+ from ...utils.sse_parser import parse_sse_stream_chunked
10
+ from .base_request_handler import BaseRequestHandler
11
+
12
+
13
+ class RequestHandler(BaseRequestHandler):
14
+ def __init__(
15
+ self,
16
+ logger: "loguru.Logger",
17
+ timeout: float,
18
+ base_url: str,
19
+ api_key: Optional[str] = None,
20
+ bearer_token: Optional[str] = None,
21
+ log_requests: bool = False,
22
+ ):
23
+ # Initialize session for synchronous requests
24
+ self.session = requests.Session()
25
+ self._finalizer = weakref.finalize(self, self._cleanup_session, self.session)
26
+
27
+ super().__init__(
28
+ logger=logger,
29
+ timeout=timeout,
30
+ api_key=api_key,
31
+ base_url=base_url,
32
+ bearer_token=bearer_token,
33
+ log_requests=log_requests,
34
+ )
35
+
36
+ @staticmethod
37
+ def _cleanup_session(session: requests.Session):
38
+ """Static method to clean up session - called by finalizer"""
39
+ if session:
40
+ session.close()
41
+
42
+ def close(self):
43
+ """
44
+ Closes the requests session to free up system resources.
45
+
46
+ This method should be called when the RequestHandler is no longer needed to ensure
47
+ proper cleanup of the underlying session and its resources.
48
+ """
49
+ self.session.close()
50
+
51
+ def _handle_exception(self, e: requests.HTTPError, url: str, correlation_id: str):
52
+ # Log the error response if enabled
53
+ if self.log_requests:
54
+ self.logger.error(
55
+ f"API Error: {e.response.status_code} {e.response.reason}\n"
56
+ f"URL: {url}\n"
57
+ f"Correlation ID: {correlation_id}"
58
+ )
59
+
60
+ # Extract error details from response if possible
61
+ error_message = "API request failed"
62
+ try:
63
+ error_data = e.response.json()
64
+ if isinstance(error_data, dict) and "message" in error_data:
65
+ error_message = error_data["message"]
66
+ elif isinstance(error_data, dict) and "error" in error_data:
67
+ error_message = error_data["error"]
68
+ except (ValueError, KeyError):
69
+ # If JSON parsing fails or expected keys are missing
70
+ error_message = f"API request failed: {str(e)}"
71
+
72
+ # Make sure sensitive auth information is not included in error messages
73
+ sanitized_message = error_message
74
+ if self.api_key and self.api_key in sanitized_message:
75
+ sanitized_message = sanitized_message.replace(self.api_key, "[REDACTED]")
76
+ if self.bearer_token and self.bearer_token in sanitized_message:
77
+ sanitized_message = sanitized_message.replace(
78
+ self.bearer_token, "[REDACTED]"
79
+ )
80
+
81
+ # Raise custom exception with status code and sanitized message
82
+ raise AiriaAPIError(
83
+ status_code=e.response.status_code, message=sanitized_message
84
+ ) from e
85
+
86
+ def make_request(
87
+ self, method: str, request_data: RequestData, return_json: bool = True
88
+ ) -> Dict[str, Any]:
89
+ """
90
+ Makes a synchronous HTTP request to the Airia API.
91
+
92
+ Args:
93
+ method (str): The HTTP method (e.g., 'GET', 'POST')
94
+ request_data: A dictionary containing the following request information:
95
+ - url: The endpoint URL for the request
96
+ - headers: HTTP headers to include in the request
97
+ - payload: The JSON payload/body for the request
98
+ - params: Optional query parameters to append to the URL
99
+ - files: Optional file data to be uploaded in the request body
100
+ - correlation_id: Unique identifier for request tracing
101
+ return_json (bool): Whether to return the response as JSON. Default is True.
102
+
103
+ Returns:
104
+ resp (Dict[str, Any]): The JSON response from the API as a dictionary.
105
+
106
+ Raises:
107
+ AiriaAPIError: If the API returns an error response, with details about the error
108
+ requests.HTTPError: For HTTP-related errors
109
+
110
+ Note:
111
+ This is an internal method used by other client methods to make API requests.
112
+ It handles logging, error handling, and API key redaction in error messages.
113
+ """
114
+ try:
115
+ # Make the request
116
+ response = self.session.request(
117
+ method=method,
118
+ url=request_data.url,
119
+ json=request_data.payload,
120
+ params=request_data.params,
121
+ headers=request_data.headers,
122
+ timeout=self.timeout,
123
+ )
124
+
125
+ # Log the response if enabled
126
+ if self.log_requests:
127
+ self.logger.info(
128
+ f"API Response: {response.status_code} {response.reason}\n"
129
+ f"URL: {request_data.url}\n"
130
+ f"Correlation ID: {request_data.correlation_id}\n"
131
+ )
132
+
133
+ # Check for HTTP errors
134
+ response.raise_for_status()
135
+
136
+ # Returns the JSON response
137
+ if return_json:
138
+ return response.json()
139
+
140
+ except requests.HTTPError as e:
141
+ self._handle_exception(e, request_data.url, request_data.correlation_id)
142
+
143
+ def make_request_stream(self, method: str, request_data: RequestData):
144
+ """
145
+ Makes a synchronous HTTP request to the Airia API.
146
+
147
+ Args:
148
+ method (str): The HTTP method (e.g., 'GET', 'POST')
149
+ request_data: A dictionary containing the following request information:
150
+ - url: The endpoint URL for the request
151
+ - headers: HTTP headers to include in the request
152
+ - payload: The JSON payload/body for the request
153
+ - params: Optional query parameters to append to the URL
154
+ - files: Optional file data to be uploaded in the request body
155
+ - correlation_id: Unique identifier for request tracing
156
+ stream (bool): If True, the response will be streamed instead of downloaded all at once
157
+
158
+ Yields:
159
+ resp (Iterator[str]): Yields chunks of the response as they are received.
160
+
161
+ Raises:
162
+ AiriaAPIError: If the API returns an error response, with details about the error
163
+ requests.HTTPError: For HTTP-related errors
164
+
165
+ Note:
166
+ This is an internal method used by other client methods to make API requests.
167
+ It handles logging, error handling, and API key redaction in error messages.
168
+ """
169
+ try:
170
+ # Make the request
171
+ response = self.session.request(
172
+ method=method,
173
+ url=request_data.url,
174
+ params=request_data.params,
175
+ json=request_data.payload,
176
+ headers=request_data.headers,
177
+ timeout=self.timeout,
178
+ stream=True,
179
+ )
180
+
181
+ # Log the response if enabled
182
+ if self.log_requests:
183
+ self.logger.info(
184
+ f"API Response: {response.status_code} {response.reason}\n"
185
+ f"URL: {request_data.url}\n"
186
+ f"Correlation ID: {request_data.correlation_id}\n"
187
+ )
188
+
189
+ # Check for HTTP errors
190
+ response.raise_for_status()
191
+
192
+ # Yields the response content as a stream
193
+ for message in parse_sse_stream_chunked(response.iter_content()):
194
+ yield message
195
+
196
+ except requests.HTTPError as e:
197
+ self._handle_exception(e, request_data.url, request_data.correlation_id)
198
+
199
+ def make_request_multipart(
200
+ self, method: str, request_data: RequestData, return_json: bool = True
201
+ ):
202
+ """
203
+ Makes a synchronous HTTP request with multipart form data to the Airia API.
204
+
205
+ Args:
206
+ method (str): The HTTP method (e.g., 'POST')
207
+ request_data: A dictionary containing the following request information:
208
+ - url: The endpoint URL for the request
209
+ - headers: HTTP headers to include in the request
210
+ - payload: The form data payload including file content
211
+ - params: Optional query parameters to append to the URL
212
+ - files: Optional file data to be uploaded in the request body
213
+ - correlation_id: Unique identifier for request tracing
214
+ return_json (bool): Whether to return the response as JSON. Default is True.
215
+
216
+ Returns:
217
+ resp (Dict[str, Any]): The JSON response from the API as a dictionary.
218
+
219
+ Raises:
220
+ AiriaAPIError: If the API returns an error response, with details about the error
221
+ requests.HTTPError: For HTTP-related errors
222
+
223
+ Note:
224
+ This is an internal method used by file upload methods to make multipart requests.
225
+ It handles multipart form data encoding, logging, and error handling.
226
+ """
227
+ try:
228
+ # Make the request
229
+ response = self.session.request(
230
+ method=method,
231
+ url=request_data.url,
232
+ files=request_data.files,
233
+ data=request_data.payload,
234
+ params=request_data.params,
235
+ headers=request_data.headers,
236
+ timeout=self.timeout,
237
+ )
238
+
239
+ # Log the response if enabled
240
+ if self.log_requests:
241
+ self.logger.info(
242
+ f"API Response: {response.status_code} {response.reason}\n"
243
+ f"URL: {request_data.url}\n"
244
+ f"Correlation ID: {request_data.correlation_id}\n"
245
+ )
246
+
247
+ # Check for HTTP errors
248
+ response.raise_for_status()
249
+
250
+ # Returns the JSON response
251
+ if return_json:
252
+ return response.json()
253
+
254
+ except requests.HTTPError as e:
255
+ self._handle_exception(e, request_data.url, request_data.correlation_id)