578 lines
20 KiB
Python
578 lines
20 KiB
Python
# Copyright 2026 Google LLC
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
# pyformat: disable
|
|
|
|
"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
|
|
|
|
from . import errors, types, utils
|
|
from ._hooks import (
|
|
AfterErrorContext,
|
|
AfterSuccessContext,
|
|
BeforeRequestContext,
|
|
HookContext,
|
|
)
|
|
from .sdkconfiguration import SDKConfiguration
|
|
from .utils import RetryConfig, SerializedRequestBody, get_body_content
|
|
import httpx
|
|
from typing import Any, Callable, List, Mapping, Optional, Tuple
|
|
from urllib.parse import parse_qs, urlparse
|
|
|
|
|
|
class BaseSDK:
|
|
sdk_configuration: SDKConfiguration
|
|
parent_ref: Optional[object] = None
|
|
"""
|
|
Reference to the root SDK instance, if any. This will prevent it from
|
|
being garbage collected while there are active streams.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
sdk_config: SDKConfiguration,
|
|
parent_ref: Optional[object] = None,
|
|
) -> None:
|
|
self.sdk_configuration = sdk_config
|
|
self.parent_ref = parent_ref
|
|
|
|
def _get_url(self, base_url, url_variables):
|
|
sdk_url, sdk_variables = self.sdk_configuration.get_server_details()
|
|
|
|
if base_url is None:
|
|
base_url = sdk_url
|
|
|
|
if url_variables is None:
|
|
url_variables = sdk_variables
|
|
|
|
return utils.template_url(base_url, url_variables)
|
|
|
|
def _coerce_timeout_ms(self, timeout: Optional[Any]) -> Optional[int]:
|
|
if timeout is None:
|
|
return None
|
|
if isinstance(timeout, (int, float)):
|
|
return int(timeout * 1000)
|
|
if isinstance(timeout, httpx.Timeout):
|
|
values = [timeout.connect, timeout.read, timeout.write, timeout.pool]
|
|
finite_values = [value for value in values if value is not None]
|
|
if not finite_values:
|
|
return None
|
|
return int(max(finite_values) * 1000)
|
|
raise TypeError("timeout must be a float, int, httpx.Timeout, or None")
|
|
|
|
def _build_request(
|
|
self,
|
|
method,
|
|
path,
|
|
base_url,
|
|
url_variables,
|
|
request,
|
|
request_body_required,
|
|
request_has_path_params,
|
|
request_has_query_params,
|
|
user_agent_header,
|
|
accept_header_value,
|
|
_globals=None,
|
|
security=None,
|
|
timeout_ms: Optional[int] = None,
|
|
get_serialized_body: Optional[
|
|
Callable[[], Optional[SerializedRequestBody]]
|
|
] = None,
|
|
url_override: Optional[str] = None,
|
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
extra_query_params: Optional[Mapping[str, Any]] = None,
|
|
allow_empty_value: Optional[List[str]] = None,
|
|
allowed_fields: Optional[List[str]] = None,
|
|
) -> httpx.Request:
|
|
client = self.sdk_configuration.client
|
|
return self._build_request_with_client(
|
|
client,
|
|
method,
|
|
path,
|
|
base_url,
|
|
url_variables,
|
|
request,
|
|
request_body_required,
|
|
request_has_path_params,
|
|
request_has_query_params,
|
|
user_agent_header,
|
|
accept_header_value,
|
|
_globals,
|
|
security,
|
|
timeout_ms,
|
|
get_serialized_body,
|
|
url_override,
|
|
http_headers,
|
|
extra_query_params,
|
|
allow_empty_value,
|
|
allowed_fields,
|
|
)
|
|
|
|
def _build_request_with_client(
|
|
self,
|
|
client,
|
|
method,
|
|
path,
|
|
base_url,
|
|
url_variables,
|
|
request,
|
|
request_body_required,
|
|
request_has_path_params,
|
|
request_has_query_params,
|
|
user_agent_header,
|
|
accept_header_value,
|
|
_globals=None,
|
|
security=None,
|
|
timeout_ms: Optional[int] = None,
|
|
get_serialized_body: Optional[
|
|
Callable[[], Optional[SerializedRequestBody]]
|
|
] = None,
|
|
url_override: Optional[str] = None,
|
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
extra_query_params: Optional[Mapping[str, Any]] = None,
|
|
allow_empty_value: Optional[List[str]] = None,
|
|
allowed_fields: Optional[List[str]] = None,
|
|
) -> httpx.Request:
|
|
query_params = {}
|
|
|
|
url = url_override
|
|
if url is None:
|
|
url = utils.generate_url(
|
|
self._get_url(base_url, url_variables),
|
|
path,
|
|
request if request_has_path_params else None,
|
|
_globals if request_has_path_params else None,
|
|
)
|
|
|
|
query_params = utils.get_query_params(
|
|
request if request_has_query_params else None,
|
|
_globals if request_has_query_params else None,
|
|
allow_empty_value,
|
|
)
|
|
else:
|
|
# Pick up the query parameter from the override so they can be
|
|
# preserved when building the request later on (necessary as of
|
|
# httpx 0.28).
|
|
parsed_override = urlparse(str(url_override))
|
|
query_params = parse_qs(parsed_override.query, keep_blank_values=True)
|
|
|
|
headers = utils.get_headers(request, _globals)
|
|
headers["Accept"] = accept_header_value
|
|
headers[user_agent_header] = self.sdk_configuration.user_agent
|
|
|
|
if security is not None:
|
|
if callable(security):
|
|
security = security()
|
|
security = utils.get_security_from_env(security, types.Security)
|
|
if security is not None:
|
|
security_headers, security_query_params = utils.get_security(
|
|
security, allowed_fields
|
|
)
|
|
headers = {**headers, **security_headers}
|
|
query_params = {**query_params, **security_query_params}
|
|
if extra_query_params is not None:
|
|
for name, value in extra_query_params.items():
|
|
if value is None:
|
|
continue
|
|
if isinstance(value, (list, tuple)):
|
|
query_params[name] = [str(v) for v in value]
|
|
else:
|
|
query_params[name] = [str(value)]
|
|
|
|
serialized_request_body = SerializedRequestBody()
|
|
if get_serialized_body is not None:
|
|
rb = get_serialized_body()
|
|
if request_body_required and rb is None:
|
|
raise ValueError("request body is required")
|
|
|
|
if rb is not None:
|
|
serialized_request_body = rb
|
|
|
|
if (
|
|
serialized_request_body.media_type is not None
|
|
and serialized_request_body.media_type
|
|
not in (
|
|
"multipart/form-data",
|
|
"multipart/mixed",
|
|
)
|
|
):
|
|
headers["content-type"] = serialized_request_body.media_type
|
|
|
|
if http_headers is not None:
|
|
for header, value in http_headers.items():
|
|
headers[header] = value
|
|
|
|
timeout = timeout_ms / 1000 if timeout_ms is not None else None
|
|
|
|
return client.build_request(
|
|
method,
|
|
url,
|
|
params=query_params,
|
|
content=serialized_request_body.content,
|
|
data=serialized_request_body.data,
|
|
files=serialized_request_body.files,
|
|
headers=headers,
|
|
timeout=timeout if timeout is not None else httpx.USE_CLIENT_DEFAULT,
|
|
)
|
|
|
|
def do_request(
|
|
self,
|
|
hook_ctx: HookContext,
|
|
request: httpx.Request,
|
|
is_error_status_code: Callable[[int], bool],
|
|
stream: bool = False,
|
|
retry_config: Optional[Tuple[RetryConfig, List[str]]] = None,
|
|
) -> httpx.Response:
|
|
client = self.sdk_configuration.client
|
|
logger = self.sdk_configuration.debug_logger
|
|
|
|
hooks = self.sdk_configuration.__dict__["_hooks"]
|
|
|
|
def do(_attempt: int = 0):
|
|
http_res = None
|
|
try:
|
|
req = hooks.before_request(BeforeRequestContext(hook_ctx), request)
|
|
if "timeout" in request.extensions and "timeout" not in req.extensions:
|
|
req.extensions["timeout"] = request.extensions["timeout"]
|
|
logger.debug(
|
|
"Request:\nMethod: %s\nURL: %s\nHeaders: %s\nBody: %s",
|
|
req.method,
|
|
req.url,
|
|
req.headers,
|
|
get_body_content(req),
|
|
)
|
|
|
|
if client is None:
|
|
raise ValueError("client is required")
|
|
|
|
http_res = client.send(req, stream=stream)
|
|
except Exception as e:
|
|
_, e = hooks.after_error(AfterErrorContext(hook_ctx), None, e)
|
|
if e is not None:
|
|
logger.debug("Request Exception", exc_info=True)
|
|
raise e
|
|
|
|
if http_res is None:
|
|
logger.debug("Raising no response SDK error")
|
|
raise errors.NoResponseError("No response received")
|
|
|
|
logger.debug(
|
|
"Response:\nStatus Code: %s\nURL: %s\nHeaders: %s\nBody: %s",
|
|
http_res.status_code,
|
|
http_res.url,
|
|
http_res.headers,
|
|
"<streaming response>" if stream else http_res.text,
|
|
)
|
|
|
|
return http_res
|
|
|
|
if retry_config is not None:
|
|
http_res = utils.retry(do, utils.Retries(retry_config[0], retry_config[1]))
|
|
else:
|
|
http_res = do()
|
|
|
|
if is_error_status_code(http_res.status_code):
|
|
result, err = hooks.after_error(AfterErrorContext(hook_ctx), http_res, None)
|
|
if err is not None:
|
|
logger.debug("Request Exception", exc_info=True)
|
|
raise err
|
|
if result is not None:
|
|
http_res = result
|
|
else:
|
|
logger.debug("Raising unexpected SDK error")
|
|
raise errors.GenAiDefaultError("Unexpected error occurred", http_res)
|
|
else:
|
|
http_res = hooks.after_success(AfterSuccessContext(hook_ctx), http_res)
|
|
|
|
return http_res
|
|
|
|
|
|
class AsyncBaseSDK:
|
|
sdk_configuration: SDKConfiguration
|
|
parent_ref: Optional[object] = None
|
|
"""
|
|
Reference to the root SDK instance, if any. This will prevent it from
|
|
being garbage collected while there are active streams.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
sdk_config: SDKConfiguration,
|
|
parent_ref: Optional[object] = None,
|
|
) -> None:
|
|
self.sdk_configuration = sdk_config
|
|
self.parent_ref = parent_ref
|
|
|
|
def _get_url(self, base_url, url_variables):
|
|
sdk_url, sdk_variables = self.sdk_configuration.get_server_details()
|
|
|
|
if base_url is None:
|
|
base_url = sdk_url
|
|
|
|
if url_variables is None:
|
|
url_variables = sdk_variables
|
|
|
|
return utils.template_url(base_url, url_variables)
|
|
|
|
def _coerce_timeout_ms(self, timeout: Optional[Any]) -> Optional[int]:
|
|
if timeout is None:
|
|
return None
|
|
if isinstance(timeout, (int, float)):
|
|
return int(timeout * 1000)
|
|
if isinstance(timeout, httpx.Timeout):
|
|
values = [timeout.connect, timeout.read, timeout.write, timeout.pool]
|
|
finite_values = [value for value in values if value is not None]
|
|
if not finite_values:
|
|
return None
|
|
return int(max(finite_values) * 1000)
|
|
raise TypeError("timeout must be a float, int, httpx.Timeout, or None")
|
|
|
|
def _build_request_async(
|
|
self,
|
|
method,
|
|
path,
|
|
base_url,
|
|
url_variables,
|
|
request,
|
|
request_body_required,
|
|
request_has_path_params,
|
|
request_has_query_params,
|
|
user_agent_header,
|
|
accept_header_value,
|
|
_globals=None,
|
|
security=None,
|
|
timeout_ms: Optional[int] = None,
|
|
get_serialized_body: Optional[
|
|
Callable[[], Optional[SerializedRequestBody]]
|
|
] = None,
|
|
url_override: Optional[str] = None,
|
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
extra_query_params: Optional[Mapping[str, Any]] = None,
|
|
allow_empty_value: Optional[List[str]] = None,
|
|
allowed_fields: Optional[List[str]] = None,
|
|
) -> httpx.Request:
|
|
client = self.sdk_configuration.async_client
|
|
return self._build_request_with_client(
|
|
client,
|
|
method,
|
|
path,
|
|
base_url,
|
|
url_variables,
|
|
request,
|
|
request_body_required,
|
|
request_has_path_params,
|
|
request_has_query_params,
|
|
user_agent_header,
|
|
accept_header_value,
|
|
_globals,
|
|
security,
|
|
timeout_ms,
|
|
get_serialized_body,
|
|
url_override,
|
|
http_headers,
|
|
extra_query_params,
|
|
allow_empty_value,
|
|
allowed_fields,
|
|
)
|
|
|
|
def _build_request_with_client(
|
|
self,
|
|
client,
|
|
method,
|
|
path,
|
|
base_url,
|
|
url_variables,
|
|
request,
|
|
request_body_required,
|
|
request_has_path_params,
|
|
request_has_query_params,
|
|
user_agent_header,
|
|
accept_header_value,
|
|
_globals=None,
|
|
security=None,
|
|
timeout_ms: Optional[int] = None,
|
|
get_serialized_body: Optional[
|
|
Callable[[], Optional[SerializedRequestBody]]
|
|
] = None,
|
|
url_override: Optional[str] = None,
|
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
extra_query_params: Optional[Mapping[str, Any]] = None,
|
|
allow_empty_value: Optional[List[str]] = None,
|
|
allowed_fields: Optional[List[str]] = None,
|
|
) -> httpx.Request:
|
|
query_params = {}
|
|
|
|
url = url_override
|
|
if url is None:
|
|
url = utils.generate_url(
|
|
self._get_url(base_url, url_variables),
|
|
path,
|
|
request if request_has_path_params else None,
|
|
_globals if request_has_path_params else None,
|
|
)
|
|
|
|
query_params = utils.get_query_params(
|
|
request if request_has_query_params else None,
|
|
_globals if request_has_query_params else None,
|
|
allow_empty_value,
|
|
)
|
|
else:
|
|
# Pick up the query parameter from the override so they can be
|
|
# preserved when building the request later on (necessary as of
|
|
# httpx 0.28).
|
|
parsed_override = urlparse(str(url_override))
|
|
query_params = parse_qs(parsed_override.query, keep_blank_values=True)
|
|
|
|
headers = utils.get_headers(request, _globals)
|
|
headers["Accept"] = accept_header_value
|
|
headers[user_agent_header] = self.sdk_configuration.user_agent
|
|
|
|
if security is not None:
|
|
if callable(security):
|
|
security = security()
|
|
security = utils.get_security_from_env(security, types.Security)
|
|
if security is not None:
|
|
security_headers, security_query_params = utils.get_security(
|
|
security, allowed_fields
|
|
)
|
|
headers = {**headers, **security_headers}
|
|
query_params = {**query_params, **security_query_params}
|
|
if extra_query_params is not None:
|
|
for name, value in extra_query_params.items():
|
|
if value is None:
|
|
continue
|
|
if isinstance(value, (list, tuple)):
|
|
query_params[name] = [str(v) for v in value]
|
|
else:
|
|
query_params[name] = [str(value)]
|
|
|
|
serialized_request_body = SerializedRequestBody()
|
|
if get_serialized_body is not None:
|
|
rb = get_serialized_body()
|
|
if request_body_required and rb is None:
|
|
raise ValueError("request body is required")
|
|
|
|
if rb is not None:
|
|
serialized_request_body = rb
|
|
|
|
if (
|
|
serialized_request_body.media_type is not None
|
|
and serialized_request_body.media_type
|
|
not in (
|
|
"multipart/form-data",
|
|
"multipart/mixed",
|
|
)
|
|
):
|
|
headers["content-type"] = serialized_request_body.media_type
|
|
|
|
if http_headers is not None:
|
|
for header, value in http_headers.items():
|
|
headers[header] = value
|
|
|
|
timeout = timeout_ms / 1000 if timeout_ms is not None else None
|
|
|
|
return client.build_request(
|
|
method,
|
|
url,
|
|
params=query_params,
|
|
content=serialized_request_body.content,
|
|
data=serialized_request_body.data,
|
|
files=serialized_request_body.files,
|
|
headers=headers,
|
|
timeout=timeout if timeout is not None else httpx.USE_CLIENT_DEFAULT,
|
|
)
|
|
|
|
async def do_request_async(
|
|
self,
|
|
hook_ctx: HookContext,
|
|
request: httpx.Request,
|
|
is_error_status_code: Callable[[int], bool],
|
|
stream: bool = False,
|
|
retry_config: Optional[Tuple[RetryConfig, List[str]]] = None,
|
|
) -> httpx.Response:
|
|
client = self.sdk_configuration.async_client
|
|
logger = self.sdk_configuration.debug_logger
|
|
|
|
async_hooks = self.sdk_configuration.__dict__["_async_hooks"]
|
|
|
|
async def do(_attempt: int = 0):
|
|
http_res = None
|
|
try:
|
|
req = await async_hooks.before_request(
|
|
BeforeRequestContext(hook_ctx), request
|
|
)
|
|
|
|
if "timeout" in request.extensions and "timeout" not in req.extensions:
|
|
req.extensions["timeout"] = request.extensions["timeout"]
|
|
logger.debug(
|
|
"Request:\nMethod: %s\nURL: %s\nHeaders: %s\nBody: %s",
|
|
req.method,
|
|
req.url,
|
|
req.headers,
|
|
get_body_content(req),
|
|
)
|
|
|
|
if client is None:
|
|
raise ValueError("client is required")
|
|
|
|
http_res = await client.send(req, stream=stream)
|
|
except Exception as e:
|
|
_, e = await async_hooks.after_error(
|
|
AfterErrorContext(hook_ctx), None, e
|
|
)
|
|
|
|
if e is not None:
|
|
logger.debug("Request Exception", exc_info=True)
|
|
raise e
|
|
|
|
if http_res is None:
|
|
logger.debug("Raising no response SDK error")
|
|
raise errors.NoResponseError("No response received")
|
|
|
|
logger.debug(
|
|
"Response:\nStatus Code: %s\nURL: %s\nHeaders: %s\nBody: %s",
|
|
http_res.status_code,
|
|
http_res.url,
|
|
http_res.headers,
|
|
"<streaming response>" if stream else http_res.text,
|
|
)
|
|
|
|
return http_res
|
|
|
|
if retry_config is not None:
|
|
http_res = await utils.retry_async(
|
|
do, utils.Retries(retry_config[0], retry_config[1])
|
|
)
|
|
else:
|
|
http_res = await do()
|
|
|
|
if is_error_status_code(http_res.status_code):
|
|
result, err = await async_hooks.after_error(
|
|
AfterErrorContext(hook_ctx), http_res, None
|
|
)
|
|
|
|
if err is not None:
|
|
logger.debug("Request Exception", exc_info=True)
|
|
raise err
|
|
if result is not None:
|
|
http_res = result
|
|
else:
|
|
logger.debug("Raising unexpected SDK error")
|
|
raise errors.GenAiDefaultError("Unexpected error occurred", http_res)
|
|
else:
|
|
http_res = await async_hooks.after_success(
|
|
AfterSuccessContext(hook_ctx), http_res
|
|
)
|
|
|
|
return http_res
|