vibe-client 0.1.0__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.
vibe_client/client.py ADDED
@@ -0,0 +1,498 @@
1
+ # Code generated by smithy-python-codegen DO NOT EDIT.
2
+
3
+ import asyncio
4
+ from asyncio import Future, iscoroutine, sleep
5
+ from copy import copy, deepcopy
6
+ from dataclasses import replace
7
+ import logging
8
+ from typing import Any, Awaitable, Callable, cast
9
+
10
+ from smithy_core import URI
11
+ from smithy_core.deserializers import DeserializeableShape
12
+ from smithy_core.endpoints import EndpointResolverParams
13
+ from smithy_core.exceptions import SmithyRetryException
14
+ from smithy_core.interceptors import (
15
+ InputContext,
16
+ Interceptor,
17
+ InterceptorChain,
18
+ OutputContext,
19
+ RequestContext,
20
+ ResponseContext,
21
+ )
22
+ from smithy_core.interfaces.exceptions import HasFault
23
+ from smithy_core.interfaces.retries import RetryErrorInfo, RetryErrorType
24
+ from smithy_core.schemas import APIOperation
25
+ from smithy_core.serializers import SerializeableShape
26
+ from smithy_core.types import TypedProperties
27
+ from smithy_http.aio.interfaces import HTTPRequest, HTTPResponse
28
+ from smithy_http.interfaces import HTTPRequestConfiguration
29
+ from smithy_http.plugins import user_agent_plugin
30
+
31
+ from .config import Config, Plugin
32
+ from .deserialize import _deserialize_query_agent
33
+ from .models import QUERY_AGENT, QueryAgentInput, QueryAgentOutput, ServiceError
34
+ from .serialize import _serialize_query_agent
35
+ from smithy_http import Field
36
+
37
+
38
+
39
+ logger = logging.getLogger(__name__)
40
+
41
+ class Vibe:
42
+ """
43
+ Client for Vibe
44
+
45
+ :param config: Optional configuration for the client. Here you can set things like the
46
+ endpoint for HTTP services or auth credentials.
47
+
48
+ :param plugins: A list of callables that modify the configuration dynamically. These
49
+ can be used to set defaults, for example.
50
+ """
51
+ def __init__(self, config: Config | None = None, plugins: list[Plugin] | None = None):
52
+ self._config = config or Config()
53
+
54
+ client_plugins: list[Plugin] = [
55
+ user_agent_plugin,
56
+ ]
57
+ if plugins:
58
+ client_plugins.extend(plugins)
59
+
60
+ for plugin in client_plugins:
61
+ plugin(self._config)
62
+
63
+ async def query_agent(self, input: QueryAgentInput, plugins: list[Plugin] | None = None) -> QueryAgentOutput:
64
+ """
65
+ Invokes the QueryAgent operation.
66
+
67
+ :param input: The operation's input.
68
+
69
+ :param plugins: A list of callables that modify the configuration dynamically.
70
+ Changes made by these plugins only apply for the duration of the operation
71
+ execution and will not affect any other operation invocations.
72
+
73
+ """
74
+ operation_plugins: list[Plugin] = [
75
+
76
+ ]
77
+ if plugins:
78
+ operation_plugins.extend(plugins)
79
+
80
+ return await self._execute_operation(
81
+ input=input,
82
+ plugins=operation_plugins,
83
+ serialize=_serialize_query_agent,
84
+ deserialize=_deserialize_query_agent,
85
+ config=self._config,
86
+ operation=QUERY_AGENT,
87
+ )
88
+
89
+ def _classify_error(
90
+ self,
91
+ *,
92
+ error: Exception,
93
+ context: ResponseContext[Any, HTTPRequest, HTTPResponse | None]
94
+ ) -> RetryErrorInfo:
95
+ logger.debug("Classifying error: %s", error)
96
+
97
+ if not isinstance(error, HasFault) and not context.transport_response:
98
+ return RetryErrorInfo(error_type=RetryErrorType.TRANSIENT)
99
+
100
+ if context.transport_response:
101
+ if context.transport_response.status in [429, 503]:
102
+ retry_after = None
103
+ retry_header = context.transport_response.fields["retry-after"]
104
+ if retry_header and retry_header.values:
105
+ retry_after = float(retry_header.values[0])
106
+ return RetryErrorInfo(error_type=RetryErrorType.THROTTLING, retry_after_hint=retry_after)
107
+
108
+ if context.transport_response.status >= 500:
109
+ return RetryErrorInfo(error_type=RetryErrorType.SERVER_ERROR)
110
+
111
+ error_type = RetryErrorType.CLIENT_ERROR
112
+ if isinstance(error, HasFault) and error.fault == "server":
113
+ error_type = RetryErrorType.SERVER_ERROR
114
+
115
+ return RetryErrorInfo(error_type=error_type)
116
+
117
+ async def _execute_operation[Input: SerializeableShape, Output: DeserializeableShape](
118
+ self,
119
+ input: Input,
120
+ plugins: list[Plugin],
121
+ serialize: Callable[[Input, Config], Awaitable[HTTPRequest]],
122
+ deserialize: Callable[[HTTPResponse, Config], Awaitable[Output]],
123
+ config: Config,
124
+ operation: APIOperation[Input, Output],
125
+ request_future: Future[RequestContext[Any, HTTPRequest]] | None = None,
126
+ response_future: Future[HTTPResponse] | None = None,
127
+ ) -> Output:
128
+ try:
129
+ return await self._handle_execution(
130
+ input, plugins, serialize, deserialize, config, operation,
131
+ request_future, response_future,
132
+ )
133
+ except Exception as e:
134
+ if request_future is not None and not request_future.done():
135
+ request_future.set_exception(ServiceError(e))
136
+ if response_future is not None and not response_future.done():
137
+ response_future.set_exception(ServiceError(e))
138
+
139
+ # Make sure every exception that we throw is an instance of ServiceError so
140
+ # customers can reliably catch everything we throw.
141
+ if not isinstance(e, ServiceError):
142
+ raise ServiceError(e) from e
143
+ raise
144
+
145
+ async def _handle_execution[Input: SerializeableShape, Output: DeserializeableShape](
146
+ self,
147
+ input: Input,
148
+ plugins: list[Plugin],
149
+ serialize: Callable[[Input, Config], Awaitable[HTTPRequest]],
150
+ deserialize: Callable[[HTTPResponse, Config], Awaitable[Output]],
151
+ config: Config,
152
+ operation: APIOperation[Input, Output],
153
+ request_future: Future[RequestContext[Any, HTTPRequest]] | None,
154
+ response_future: Future[HTTPResponse] | None,
155
+ ) -> Output:
156
+ operation_name = operation.schema.id.name
157
+ logger.debug('Making request for operation "%s" with parameters: %s', operation_name, input)
158
+ config = deepcopy(config)
159
+ for plugin in plugins:
160
+ plugin(config)
161
+
162
+ input_context = InputContext(request=input, properties=TypedProperties({"config": config}))
163
+ transport_request: HTTPRequest | None = None
164
+ output_context: OutputContext[Input, Output, HTTPRequest | None, HTTPResponse | None] | None = None
165
+
166
+ client_interceptors = cast(
167
+ list[Interceptor[Input, Output, HTTPRequest, HTTPResponse]], list(config.interceptors)
168
+ )
169
+ interceptor_chain = InterceptorChain(client_interceptors)
170
+
171
+ try:
172
+ # Step 1: Invoke read_before_execution
173
+ interceptor_chain.read_before_execution(input_context)
174
+
175
+ # Step 2: Invoke the modify_before_serialization hooks
176
+ input_context = replace(
177
+ input_context,
178
+ request=interceptor_chain.modify_before_serialization(input_context)
179
+ )
180
+
181
+ # Step 3: Invoke the read_before_serialization hooks
182
+ interceptor_chain.read_before_serialization(input_context)
183
+
184
+ # Step 4: Serialize the request
185
+ logger.debug("Serializing request for: %s", input_context.request)
186
+ transport_request = await serialize(input_context.request, config)
187
+ request_context = RequestContext(
188
+ request=input_context.request,
189
+ transport_request=transport_request,
190
+ properties=input_context.properties,
191
+ )
192
+ logger.debug("Serialization complete. Transport request: %s", request_context.transport_request)
193
+
194
+ # Step 5: Invoke read_after_serialization
195
+ interceptor_chain.read_after_serialization(request_context)
196
+
197
+ # Step 6: Invoke modify_before_retry_loop
198
+ request_context = replace(
199
+ request_context,
200
+ transport_request=interceptor_chain.modify_before_retry_loop(request_context)
201
+ )
202
+
203
+ # Step 7: Acquire the retry token.
204
+ retry_strategy = config.retry_strategy
205
+ retry_token = retry_strategy.acquire_initial_retry_token()
206
+
207
+ while True:
208
+ # Make an attempt
209
+ output_context = await self._handle_attempt(
210
+ deserialize,
211
+ interceptor_chain,
212
+ replace(
213
+ request_context,
214
+ transport_request = copy(request_context.transport_request)
215
+ ),
216
+ config,
217
+ operation,
218
+ request_future,
219
+ )
220
+
221
+ if isinstance(output_context.response, Exception):
222
+ # Step 7u: Reacquire retry token if the attempt failed
223
+ try:
224
+ retry_token = retry_strategy.refresh_retry_token_for_retry(
225
+ token_to_renew=retry_token,
226
+ error_info=self._classify_error(
227
+ error=output_context.response,
228
+ context=output_context,
229
+ )
230
+ )
231
+ except SmithyRetryException:
232
+ raise output_context.response
233
+ logger.debug(
234
+ "Retry needed. Attempting request #%s in %.4f seconds.",
235
+ retry_token.retry_count + 1,
236
+ retry_token.retry_delay
237
+ )
238
+ await sleep(retry_token.retry_delay)
239
+ current_body = output_context.transport_request.body
240
+ if (seek := getattr(current_body, "seek", None)) is not None:
241
+ if iscoroutine((result := seek(0))):
242
+ await result
243
+ else:
244
+ # Step 8: Invoke record_success
245
+ retry_strategy.record_success(token=retry_token)
246
+ if response_future is not None:
247
+ response_future.set_result(
248
+ output_context.transport_response # type: ignore
249
+ )
250
+ break
251
+ except Exception as e:
252
+ if output_context is not None:
253
+ logger.exception("Exception occurred while handling: %s", output_context.response)
254
+ output_context = replace(output_context, response=e)
255
+ else:
256
+ output_context = OutputContext(
257
+ request=input_context.request,
258
+ response=e,
259
+ transport_request=transport_request,
260
+ transport_response=None,
261
+ properties=input_context.properties
262
+ )
263
+
264
+ return await self._finalize_execution(interceptor_chain, output_context)
265
+
266
+ async def _handle_attempt[Input: SerializeableShape, Output: DeserializeableShape](
267
+ self,
268
+ deserialize: Callable[[HTTPResponse, Config], Awaitable[Output]],
269
+ interceptor: Interceptor[Input, Output, HTTPRequest, HTTPResponse],
270
+ context: RequestContext[Input, HTTPRequest],
271
+ config: Config,
272
+ operation: APIOperation[Input, Output],
273
+ request_future: Future[RequestContext[Input, HTTPRequest]] | None,
274
+ ) -> OutputContext[Input, Output, HTTPRequest, HTTPResponse | None]:
275
+ transport_response: HTTPResponse | None = None
276
+ try:
277
+ # Step 7a: Invoke read_before_attempt
278
+ interceptor.read_before_attempt(context)
279
+
280
+ # Step 7f: Invoke endpoint_resolver.resolve_endpoint
281
+ endpoint_resolver_parameters = EndpointResolverParams(
282
+ operation=operation,
283
+ input=context.request,
284
+ context=context.properties
285
+ )
286
+ logger.debug("Calling endpoint resolver with parameters: %s", endpoint_resolver_parameters)
287
+ endpoint = await config.endpoint_resolver.resolve_endpoint(
288
+ endpoint_resolver_parameters
289
+ )
290
+ logger.debug("Endpoint resolver result: %s", endpoint)
291
+ if not endpoint.uri.path:
292
+ path = ""
293
+ elif endpoint.uri.path.endswith("/"):
294
+ path = endpoint.uri.path[:-1]
295
+ else:
296
+ path = endpoint.uri.path
297
+ if context.transport_request.destination.path:
298
+ path += context.transport_request.destination.path
299
+ context.transport_request.destination = URI(
300
+ scheme=endpoint.uri.scheme,
301
+ host=context.transport_request.destination.host + endpoint.uri.host,
302
+ path=path,
303
+ port=endpoint.uri.port,
304
+ query=context.transport_request.destination.query,
305
+ )
306
+
307
+ if (headers := endpoint.properties.get("headers")) is not None:
308
+ context.transport_request.fields.extend(headers)
309
+
310
+ # Step 7g: Invoke modify_before_signing
311
+ context = replace(
312
+ context,
313
+ transport_request=interceptor.modify_before_signing(context)
314
+ )
315
+
316
+ # Step 7h: Invoke read_before_signing
317
+ interceptor.read_before_signing(context)
318
+
319
+ # Step 7j: Invoke read_after_signing
320
+ interceptor.read_after_signing(context)
321
+
322
+ # Step 7k: Invoke modify_before_transmit
323
+ context = replace(
324
+ context,
325
+ transport_request=interceptor.modify_before_transmit(context)
326
+ )
327
+
328
+ # Step 7l: Invoke read_before_transmit
329
+ interceptor.read_before_transmit(context)
330
+
331
+ # Step 7m: Invoke http_client.send
332
+ request_config = config.http_request_config or HTTPRequestConfiguration()
333
+ logger.debug("HTTP request config: %s", request_config)
334
+ logger.debug("Sending HTTP request: %s", context.transport_request)
335
+
336
+ if request_future is not None:
337
+ response_task = asyncio.create_task(config.http_client.send(
338
+ request=context.transport_request,
339
+ request_config=request_config,
340
+ ))
341
+ request_future.set_result(context)
342
+ transport_response = await response_task
343
+ else:
344
+ transport_response = await config.http_client.send(
345
+ request=context.transport_request,
346
+ request_config=request_config,
347
+ )
348
+
349
+ response_context = ResponseContext(
350
+ request=context.request,
351
+ transport_request=context.transport_request,
352
+ transport_response=transport_response,
353
+ properties=context.properties
354
+ )
355
+ logger.debug("Received HTTP response: %s", response_context.transport_response)
356
+
357
+ # Step 7n: Invoke read_after_transmit
358
+ interceptor.read_after_transmit(response_context)
359
+
360
+ # Step 7o: Invoke modify_before_deserialization
361
+ response_context = replace(
362
+ response_context,
363
+ transport_response=interceptor.modify_before_deserialization(response_context)
364
+ )
365
+
366
+ # Step 7p: Invoke read_before_deserialization
367
+ interceptor.read_before_deserialization(response_context)
368
+
369
+ # Step 7q: deserialize
370
+ logger.debug("Deserializing transport response: %s", response_context.transport_response)
371
+ output = await deserialize(
372
+ response_context.transport_response, config
373
+ )
374
+ output_context = OutputContext(
375
+ request=response_context.request,
376
+ response=output,
377
+ transport_request=response_context.transport_request,
378
+ transport_response=response_context.transport_response,
379
+ properties=response_context.properties
380
+ )
381
+ logger.debug("Deserialization complete. Response: %s", output_context.response)
382
+
383
+ # Step 7r: Invoke read_after_deserialization
384
+ interceptor.read_after_deserialization(output_context)
385
+ except Exception as e:
386
+ output_context: OutputContext[Input, Output, HTTPRequest, HTTPResponse] = OutputContext(
387
+ request=context.request,
388
+ response=e, # type: ignore
389
+ transport_request=context.transport_request,
390
+ transport_response=transport_response,
391
+ properties=context.properties
392
+ )
393
+
394
+ return await self._finalize_attempt(interceptor, output_context)
395
+
396
+ async def _finalize_attempt[Input: SerializeableShape, Output: DeserializeableShape](
397
+ self,
398
+ interceptor: Interceptor[Input, Output, HTTPRequest, HTTPResponse],
399
+ context: OutputContext[Input, Output, HTTPRequest, HTTPResponse | None],
400
+ ) -> OutputContext[Input, Output, HTTPRequest, HTTPResponse | None]:
401
+ # Step 7s: Invoke modify_before_attempt_completion
402
+ try:
403
+ context = replace(
404
+ context,
405
+ response=interceptor.modify_before_attempt_completion(context)
406
+ )
407
+ except Exception as e:
408
+ logger.exception("Exception occurred while handling: %s", context.response)
409
+ context = replace(context, response=e)
410
+
411
+ # Step 7t: Invoke read_after_attempt
412
+ try:
413
+ interceptor.read_after_attempt(context)
414
+ except Exception as e:
415
+ context = replace(context, response=e)
416
+
417
+ return context
418
+
419
+ async def _finalize_execution[Input: SerializeableShape, Output: DeserializeableShape](
420
+ self,
421
+ interceptor: Interceptor[Input, Output, HTTPRequest, HTTPResponse],
422
+ context: OutputContext[Input, Output, HTTPRequest | None, HTTPResponse | None],
423
+ ) -> Output:
424
+ try:
425
+ # Step 9: Invoke modify_before_completion
426
+ context = replace(
427
+ context,
428
+ response=interceptor.modify_before_completion(context)
429
+ )
430
+
431
+ # Step 10: Invoke trace_probe.dispatch_events
432
+ try:
433
+ pass
434
+ except Exception as e:
435
+ # log and ignore exceptions
436
+ logger.exception("Exception occurred while dispatching trace events: %s", e)
437
+ pass
438
+ except Exception as e:
439
+ logger.exception("Exception occurred while handling: %s", context.response)
440
+ context = replace(context, response=e)
441
+
442
+ # Step 11: Invoke read_after_execution
443
+ try:
444
+ interceptor.read_after_execution(context)
445
+ except Exception as e:
446
+ context = replace(context, response=e)
447
+
448
+ # Step 12: Return / throw
449
+ if isinstance(context.response, Exception):
450
+ raise context.response
451
+
452
+ # We may want to add some aspects of this context to the output types so we can
453
+ # return it to the end-users.
454
+ return context.response
455
+
456
+
457
+
458
+ class ApiKeyInterceptor(Interceptor):
459
+ def __init__(self, api_key: str):
460
+ self.api_key = api_key
461
+
462
+ def modify_before_signing(self, context: RequestContext) -> HTTPRequest:
463
+ """Add the x-api-key header to the request before it's signed."""
464
+
465
+ request = context.transport_request
466
+ # Add the x-api-key header directly
467
+ request.fields.set_field(Field(name="x-api-key", values=[str(self.api_key)]))
468
+ return context.transport_request
469
+
470
+ class VibeClient(Vibe):
471
+ """
472
+ An extension of the Vibe client with built-in API key authentication.
473
+ """
474
+
475
+ def __init__(self, api_key: str, endpoint: str = "https://us-east-1.vibe.api.astartech.ai/", **kwargs):
476
+ """
477
+ Initialize a Vibe client with API key.
478
+
479
+ Args:
480
+ api_key: The API key for authentication
481
+ endpoint: The API endpoint URI (default: https://us-east-1.vibe.api.astartech.ai/)
482
+ **kwargs: Additional arguments to pass to the Vibe constructor
483
+
484
+ Raises:
485
+ ValueError: If api_key is None or empty
486
+ """
487
+ # Check that api_key is not None or empty
488
+ if not api_key:
489
+ raise ValueError("api_key cannot be None or empty")
490
+
491
+ # Create config with API key interceptor
492
+ config = Config(
493
+ endpoint_uri=endpoint,
494
+ interceptors=[ApiKeyInterceptor(api_key)]
495
+ )
496
+
497
+ # Initialize the parent Vibe class with our config
498
+ super().__init__(config=config, **kwargs)
vibe_client/config.py ADDED
@@ -0,0 +1,74 @@
1
+ # Code generated by smithy-python-codegen DO NOT EDIT.
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Callable, TypeAlias, Union
5
+
6
+ from smithy_core.aio.endpoints import StaticEndpointResolver
7
+ from smithy_core.aio.interfaces import EndpointResolver as _EndpointResolver
8
+ from smithy_core.interceptors import Interceptor
9
+ from smithy_core.interfaces import URI
10
+ from smithy_core.interfaces.retries import RetryStrategy
11
+ from smithy_core.retries import SimpleRetryStrategy
12
+ from smithy_http.aio.aiohttp import AIOHTTPClient
13
+ from smithy_http.aio.interfaces import HTTPClient
14
+ from smithy_http.interfaces import HTTPRequestConfiguration
15
+
16
+ from .models import QueryAgentInput, QueryAgentOutput
17
+
18
+
19
+ _ServiceInterceptor = Union[Interceptor[QueryAgentInput, QueryAgentOutput, Any, Any]]
20
+ @dataclass(init=False)
21
+ class Config:
22
+ """Configuration for Vibe."""
23
+
24
+ endpoint_resolver: _EndpointResolver
25
+ endpoint_uri: str | URI | None
26
+ http_client: HTTPClient
27
+ http_request_config: HTTPRequestConfiguration | None
28
+ interceptors: list[_ServiceInterceptor]
29
+ retry_strategy: RetryStrategy
30
+
31
+ def __init__(
32
+ self,
33
+ *,
34
+ endpoint_resolver: _EndpointResolver | None = None,
35
+ endpoint_uri: str | URI | None = None,
36
+ http_client: HTTPClient | None = None,
37
+ http_request_config: HTTPRequestConfiguration | None = None,
38
+ interceptors: list[_ServiceInterceptor] | None = None,
39
+ retry_strategy: RetryStrategy | None = None,
40
+ ):
41
+ """Constructor.
42
+
43
+ :param endpoint_resolver:
44
+ The endpoint resolver used to resolve the final endpoint per-operation based on
45
+ the configuration.
46
+
47
+ :param endpoint_uri:
48
+ A static URI to route requests to.
49
+
50
+ :param http_client:
51
+ The HTTP client used to make requests.
52
+
53
+ :param http_request_config:
54
+ Configuration for individual HTTP requests.
55
+
56
+ :param interceptors:
57
+ The list of interceptors, which are hooks that are called during the execution
58
+ of a request.
59
+
60
+ :param retry_strategy:
61
+ The retry strategy for issuing retry tokens and computing retry delays.
62
+
63
+ """
64
+ self.endpoint_resolver = endpoint_resolver or StaticEndpointResolver()
65
+ self.endpoint_uri = endpoint_uri
66
+ self.http_client = http_client or AIOHTTPClient()
67
+ self.http_request_config = http_request_config
68
+ self.interceptors = interceptors or []
69
+ self.retry_strategy = retry_strategy or SimpleRetryStrategy()
70
+
71
+ #
72
+ # A callable that allows customizing the config object on each request.
73
+ #
74
+ Plugin: TypeAlias = Callable[[Config], None]
@@ -0,0 +1,115 @@
1
+ # Code generated by smithy-python-codegen DO NOT EDIT.
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ from smithy_core.documents import DocumentValue
7
+ from smithy_core.types import TimestampFormat
8
+ from smithy_http.aio.interfaces import HTTPResponse
9
+ from smithy_http.aio.restjson import parse_rest_json_error_info
10
+ from smithy_json import JSONCodec
11
+
12
+ from .config import Config
13
+ from .models import (
14
+ ApiError,
15
+ QueryAgentOutput,
16
+ UnauthorizedException,
17
+ UnknownApiError,
18
+ ValidationException,
19
+ VibeValidationException,
20
+ )
21
+
22
+
23
+ async def _deserialize_query_agent(http_response: HTTPResponse, config: Config) -> QueryAgentOutput:
24
+ if http_response.status != 200 and http_response.status >= 300:
25
+ raise await _deserialize_error_query_agent(http_response, config)
26
+
27
+ kwargs: dict[str, Any] = {}
28
+
29
+ body = await http_response.consume_body_async()
30
+ if body:
31
+ codec = JSONCodec(default_timestamp_format=TimestampFormat.EPOCH_SECONDS)
32
+ deserializer = codec.create_deserializer(body)
33
+ body_kwargs = QueryAgentOutput.deserialize_kwargs(deserializer)
34
+ kwargs.update(body_kwargs)
35
+
36
+ return QueryAgentOutput(**kwargs)
37
+
38
+ async def _deserialize_error_query_agent(http_response: HTTPResponse, config: Config) -> ApiError:
39
+ code, message, parsed_body = await parse_rest_json_error_info(http_response)
40
+
41
+ match code.lower():
42
+ case "unauthorizedexception":
43
+ return await _deserialize_error_unauthorized_exception(http_response, config, parsed_body, message)
44
+
45
+ case "validationexception":
46
+ return await _deserialize_error_validation_exception(http_response, config, parsed_body, message)
47
+
48
+ case "vibevalidationexception":
49
+ return await _deserialize_error_vibe_validation_exception(http_response, config, parsed_body, message)
50
+
51
+ case _:
52
+ return UnknownApiError(f"{code}: {message}")
53
+
54
+ async def _deserialize_error_validation_exception(
55
+ http_response: HTTPResponse,
56
+ config: Config,
57
+ parsed_body: dict[str, DocumentValue] | None,
58
+ default_message: str,
59
+ ) -> ValidationException:
60
+ kwargs: dict[str, Any] = {"message": default_message}
61
+
62
+ if parsed_body is None:
63
+ body = await http_response.consume_body_async()
64
+ else:
65
+ body = json.dumps(parsed_body).encode('utf-8')
66
+
67
+ if body:
68
+ codec = JSONCodec(default_timestamp_format=TimestampFormat.EPOCH_SECONDS)
69
+ deserializer = codec.create_deserializer(body)
70
+ body_kwargs = ValidationException.deserialize_kwargs(deserializer)
71
+ kwargs.update(body_kwargs)
72
+
73
+ return ValidationException(**kwargs)
74
+
75
+ async def _deserialize_error_unauthorized_exception(
76
+ http_response: HTTPResponse,
77
+ config: Config,
78
+ parsed_body: dict[str, DocumentValue] | None,
79
+ default_message: str,
80
+ ) -> UnauthorizedException:
81
+ kwargs: dict[str, Any] = {"message": default_message}
82
+
83
+ if parsed_body is None:
84
+ body = await http_response.consume_body_async()
85
+ else:
86
+ body = json.dumps(parsed_body).encode('utf-8')
87
+
88
+ if body:
89
+ codec = JSONCodec(default_timestamp_format=TimestampFormat.EPOCH_SECONDS)
90
+ deserializer = codec.create_deserializer(body)
91
+ body_kwargs = UnauthorizedException.deserialize_kwargs(deserializer)
92
+ kwargs.update(body_kwargs)
93
+
94
+ return UnauthorizedException(**kwargs)
95
+
96
+ async def _deserialize_error_vibe_validation_exception(
97
+ http_response: HTTPResponse,
98
+ config: Config,
99
+ parsed_body: dict[str, DocumentValue] | None,
100
+ default_message: str,
101
+ ) -> VibeValidationException:
102
+ kwargs: dict[str, Any] = {"message": default_message}
103
+
104
+ if parsed_body is None:
105
+ body = await http_response.consume_body_async()
106
+ else:
107
+ body = json.dumps(parsed_body).encode('utf-8')
108
+
109
+ if body:
110
+ codec = JSONCodec(default_timestamp_format=TimestampFormat.EPOCH_SECONDS)
111
+ deserializer = codec.create_deserializer(body)
112
+ body_kwargs = VibeValidationException.deserialize_kwargs(deserializer)
113
+ kwargs.update(body_kwargs)
114
+
115
+ return VibeValidationException(**kwargs)