feat(api): api update

This commit is contained in:
stainless-app[bot]
2025-07-17 11:30:22 +00:00
parent 7e83d753c4
commit 3df60b0923
79 changed files with 526 additions and 111 deletions
+23 -3
View File
@@ -7,12 +7,17 @@ on:
- 'integrated/**'
- 'stl-preview-head/**'
- 'stl-preview-base/**'
pull_request:
branches-ignore:
- 'stl-preview-head/**'
- 'stl-preview-base/**'
jobs:
lint:
timeout-minutes: 10
name: lint
runs-on: ${{ github.repository == 'stainless-sdks/hanzo-ai-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@v4
@@ -30,10 +35,10 @@ jobs:
- name: Run lints
run: ./scripts/lint
upload:
if: github.repository == 'stainless-sdks/hanzo-ai-python'
build:
if: github.repository == 'stainless-sdks/hanzo-ai-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork)
timeout-minutes: 10
name: upload
name: build
permissions:
contents: read
id-token: write
@@ -41,6 +46,20 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install Rye
run: |
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Install dependencies
run: rye sync --all-features
- name: Run build
run: rye build
- name: Get GitHub OIDC Token
id: github-oidc
uses: actions/github-script@v6
@@ -58,6 +77,7 @@ jobs:
timeout-minutes: 10
name: test
runs-on: ${{ github.repository == 'stainless-sdks/hanzo-ai-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@v4
+2 -2
View File
@@ -1,4 +1,4 @@
configured_endpoints: 188
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hanzo-industries-inc%2Fhanzo-ai-ec4be99f95dc46e9442eb60f233b2bff271d6f5bf57d7c61a52bc4804f55bbd1.yml
openapi_spec_hash: 87bc62c36bb6028ffd1f3e54a2809099
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hanzo-industries-inc%2Fhanzo-ai-2d6e1036fb1eea7e95cafc281141ead9ef77796e43711b17edccaab67c5e791a.yml
openapi_spec_hash: 12501774d0127be4ec1812d613a58e97
config_hash: 830747463ff4d018b5633ce511e88558
+33 -2
View File
@@ -1,6 +1,7 @@
# Hanzo Python API library
[![PyPI version](https://img.shields.io/pypi/v/hanzoai.svg)](https://pypi.org/project/hanzoai/)
<!-- prettier-ignore -->
[![PyPI version](https://img.shields.io/pypi/v/hanzoai.svg?label=pypi%20(stable))](https://pypi.org/project/hanzoai/)
The Hanzo Python library provides convenient access to the Hanzo REST API from any Python 3.8+
application. The library includes type definitions for all request params and response fields,
@@ -64,6 +65,36 @@ asyncio.run(main())
Functionality between the synchronous and asynchronous clients is otherwise identical.
### With aiohttp
By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.
You can enable this by installing `aiohttp`:
```sh
# install from PyPI
pip install hanzoai[aiohttp]
```
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
```python
import asyncio
from hanzoai import DefaultAioHttpClient
from hanzoai import AsyncHanzo
async def main() -> None:
async with AsyncHanzo(
api_key="My API Key",
http_client=DefaultAioHttpClient(),
) as client:
response = await client.get_home()
asyncio.run(main())
```
## Using types
Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:
@@ -172,7 +203,7 @@ client.with_options(max_retries=5).get_home()
### Timeouts
By default requests time out after 1 minute. You can configure this with a `timeout` option,
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
```python
from hanzoai import Hanzo
+1 -1
View File
@@ -3,7 +3,7 @@
errors=()
if [ -z "${PYPI_TOKEN}" ]; then
errors+=("The HANZO_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
fi
lenErrors=${#errors[@]}
+5 -1
View File
@@ -24,6 +24,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Operating System :: OS Independent",
"Operating System :: POSIX",
"Operating System :: MacOS",
@@ -37,6 +38,8 @@ classifiers = [
Homepage = "https://github.com/hanzoai/python-sdk"
Repository = "https://github.com/hanzoai/python-sdk"
[project.optional-dependencies]
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"]
[tool.rye]
managed = true
@@ -54,6 +57,7 @@ dev-dependencies = [
"importlib-metadata>=6.7.0",
"rich>=13.7.1",
"nest_asyncio==1.6.0",
"pytest-xdist>=3.6.1",
]
[tool.rye.scripts]
@@ -125,7 +129,7 @@ replacement = '[\1](https://github.com/hanzoai/python-sdk/tree/main/\g<2>)'
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--tb=short"
addopts = "--tb=short -n auto"
xfail_strict = true
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "session"
+33 -2
View File
@@ -10,6 +10,13 @@
# universal: false
-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
aiohttp==3.12.8
# via hanzoai
# via httpx-aiohttp
aiosignal==1.3.2
# via aiohttp
annotated-types==0.6.0
# via pydantic
anyio==4.4.0
@@ -17,6 +24,10 @@ anyio==4.4.0
# via httpx
argcomplete==3.1.2
# via nox
async-timeout==5.0.1
# via aiohttp
attrs==25.3.0
# via aiohttp
certifi==2023.7.22
# via httpcore
# via httpx
@@ -30,18 +41,27 @@ distro==1.8.0
exceptiongroup==1.2.2
# via anyio
# via pytest
execnet==2.1.1
# via pytest-xdist
filelock==3.12.4
# via virtualenv
h11==0.14.0
frozenlist==1.6.2
# via aiohttp
# via aiosignal
h11==0.16.0
# via httpcore
httpcore==1.0.2
httpcore==1.0.9
# via httpx
httpx==0.28.1
# via hanzoai
# via httpx-aiohttp
# via respx
httpx-aiohttp==0.1.8
# via hanzoai
idna==3.4
# via anyio
# via httpx
# via yarl
importlib-metadata==7.0.0
iniconfig==2.0.0
# via pytest
@@ -49,6 +69,9 @@ markdown-it-py==3.0.0
# via rich
mdurl==0.1.2
# via markdown-it-py
multidict==6.4.4
# via aiohttp
# via yarl
mypy==1.14.1
mypy-extensions==1.0.0
# via mypy
@@ -63,6 +86,9 @@ platformdirs==3.11.0
# via virtualenv
pluggy==1.5.0
# via pytest
propcache==0.3.1
# via aiohttp
# via yarl
pydantic==2.10.3
# via hanzoai
pydantic-core==2.27.1
@@ -72,7 +98,9 @@ pygments==2.18.0
pyright==1.1.399
pytest==8.3.3
# via pytest-asyncio
# via pytest-xdist
pytest-asyncio==0.24.0
pytest-xdist==3.7.0
python-dateutil==2.8.2
# via time-machine
pytz==2023.3.post1
@@ -94,11 +122,14 @@ tomli==2.0.2
typing-extensions==4.12.2
# via anyio
# via hanzoai
# via multidict
# via mypy
# via pydantic
# via pydantic-core
# via pyright
virtualenv==20.24.5
# via nox
yarl==1.20.0
# via aiohttp
zipp==3.17.0
# via importlib-metadata
+29 -2
View File
@@ -10,11 +10,22 @@
# universal: false
-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
aiohttp==3.12.8
# via hanzoai
# via httpx-aiohttp
aiosignal==1.3.2
# via aiohttp
annotated-types==0.6.0
# via pydantic
anyio==4.4.0
# via hanzoai
# via httpx
async-timeout==5.0.1
# via aiohttp
attrs==25.3.0
# via aiohttp
certifi==2023.7.22
# via httpcore
# via httpx
@@ -22,15 +33,28 @@ distro==1.8.0
# via hanzoai
exceptiongroup==1.2.2
# via anyio
h11==0.14.0
frozenlist==1.6.2
# via aiohttp
# via aiosignal
h11==0.16.0
# via httpcore
httpcore==1.0.2
httpcore==1.0.9
# via httpx
httpx==0.28.1
# via hanzoai
# via httpx-aiohttp
httpx-aiohttp==0.1.8
# via hanzoai
idna==3.4
# via anyio
# via httpx
# via yarl
multidict==6.4.4
# via aiohttp
# via yarl
propcache==0.3.1
# via aiohttp
# via yarl
pydantic==2.10.3
# via hanzoai
pydantic-core==2.27.1
@@ -41,5 +65,8 @@ sniffio==1.3.0
typing-extensions==4.12.2
# via anyio
# via hanzoai
# via multidict
# via pydantic
# via pydantic-core
yarl==1.20.0
# via aiohttp
+7 -5
View File
@@ -1,7 +1,9 @@
#!/usr/bin/env bash
set -exuo pipefail
RESPONSE=$(curl -X POST "$URL" \
FILENAME=$(basename dist/*.whl)
RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \
-H "Authorization: Bearer $AUTH" \
-H "Content-Type: application/json")
@@ -12,13 +14,13 @@ if [[ "$SIGNED_URL" == "null" ]]; then
exit 1
fi
UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \
-H "Content-Type: application/gzip" \
--data-binary @- "$SIGNED_URL" 2>&1)
UPLOAD_RESPONSE=$(curl -v -X PUT \
-H "Content-Type: binary/octet-stream" \
--data-binary "@dist/$FILENAME" "$SIGNED_URL" 2>&1)
if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
echo -e "\033[32mUploaded build to Stainless storage.\033[0m"
echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/hanzo-ai-python/$SHA'\033[0m"
echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/hanzo-ai-python/$SHA/$FILENAME'\033[0m"
else
echo -e "\033[31mFailed to upload artifact.\033[0m"
exit 1
+2 -1
View File
@@ -37,7 +37,7 @@ from ._exceptions import (
UnprocessableEntityError,
APIResponseValidationError,
)
from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient
from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
from ._utils._logs import setup_logging as _setup_logging
__all__ = [
@@ -80,6 +80,7 @@ __all__ = [
"DEFAULT_CONNECTION_LIMITS",
"DefaultHttpxClient",
"DefaultAsyncHttpxClient",
"DefaultAioHttpClient",
]
if not _t.TYPE_CHECKING:
+47 -4
View File
@@ -529,6 +529,15 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
# work around https://github.com/encode/httpx/discussions/2880
kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}
is_body_allowed = options.method.lower() != "get"
if is_body_allowed:
kwargs["json"] = json_data if is_given(json_data) else None
kwargs["files"] = files
else:
headers.pop("Content-Type", None)
kwargs.pop("data", None)
# TODO: report this error to httpx
return self._client.build_request( # pyright: ignore[reportUnknownMemberType]
headers=headers,
@@ -540,8 +549,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
# so that passing a `TypedDict` doesn't cause an error.
# https://github.com/microsoft/pyright/issues/3526#event-6715453066
params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
json=json_data if is_given(json_data) else None,
files=files,
**kwargs,
)
@@ -1071,7 +1078,14 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
) -> ResponseT:
origin = get_origin(cast_to) or cast_to
if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse):
if (
inspect.isclass(origin)
and issubclass(origin, BaseAPIResponse)
# we only want to actually return the custom BaseAPIResponse class if we're
# returning the raw response, or if we're not streaming SSE, as if we're streaming
# SSE then `cast_to` doesn't actively reflect the type we need to parse into
and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
):
if not issubclass(origin, APIResponse):
raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}")
@@ -1282,6 +1296,24 @@ class _DefaultAsyncHttpxClient(httpx.AsyncClient):
super().__init__(**kwargs)
try:
import httpx_aiohttp
except ImportError:
class _DefaultAioHttpClient(httpx.AsyncClient):
def __init__(self, **_kwargs: Any) -> None:
raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra")
else:
class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore
def __init__(self, **kwargs: Any) -> None:
kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
kwargs.setdefault("follow_redirects", True)
super().__init__(**kwargs)
if TYPE_CHECKING:
DefaultAsyncHttpxClient = httpx.AsyncClient
"""An alias to `httpx.AsyncClient` that provides the same defaults that this SDK
@@ -1290,8 +1322,12 @@ if TYPE_CHECKING:
This is useful because overriding the `http_client` with your own instance of
`httpx.AsyncClient` will result in httpx's defaults being used, not ours.
"""
DefaultAioHttpClient = httpx.AsyncClient
"""An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`."""
else:
DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient
DefaultAioHttpClient = _DefaultAioHttpClient
class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient):
@@ -1574,7 +1610,14 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
) -> ResponseT:
origin = get_origin(cast_to) or cast_to
if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse):
if (
inspect.isclass(origin)
and issubclass(origin, BaseAPIResponse)
# we only want to actually return the custom BaseAPIResponse class if we're
# returning the raw response, or if we're not streaming SSE, as if we're streaming
# SSE then `cast_to` doesn't actively reflect the type we need to parse into
and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
):
if not issubclass(origin, AsyncAPIResponse):
raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}")
+8 -5
View File
@@ -2,9 +2,10 @@ from __future__ import annotations
import os
import inspect
from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast
from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
from datetime import date, datetime
from typing_extensions import (
List,
Unpack,
Literal,
ClassVar,
@@ -366,7 +367,7 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object:
if type_ is None:
raise RuntimeError(f"Unexpected field type is None for {key}")
return construct_type(value=value, type_=type_)
return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))
def is_basemodel(type_: type) -> bool:
@@ -420,7 +421,7 @@ def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T:
return cast(_T, construct_type(value=value, type_=type_))
def construct_type(*, value: object, type_: object) -> object:
def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object:
"""Loose coercion to the expected type with construction of nested values.
If the given value does not match the expected type then it is returned as-is.
@@ -438,8 +439,10 @@ def construct_type(*, value: object, type_: object) -> object:
type_ = type_.__value__ # type: ignore[unreachable]
# unwrap `Annotated[T, ...]` -> `T`
if is_annotated_type(type_):
meta: tuple[Any, ...] = get_args(type_)[1:]
if metadata is not None:
meta: tuple[Any, ...] = tuple(metadata)
elif is_annotated_type(type_):
meta = get_args(type_)[1:]
type_ = extract_type_arg(type_, 0)
else:
meta = tuple()
+3 -1
View File
@@ -46,7 +46,9 @@ class TestSpeech:
class TestAsyncSpeech:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
@@ -52,7 +52,9 @@ class TestTranscriptions:
class TestAsyncTranscriptions:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -69,7 +69,9 @@ class TestCancel:
class TestAsyncCancel:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -46,7 +46,9 @@ class TestRedis:
class TestAsyncRedis:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -54,7 +54,9 @@ class TestCompletions:
class TestAsyncCompletions:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
@@ -173,7 +173,9 @@ class TestPassThroughEndpoint:
class TestAsyncPassThroughEndpoint:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -60,7 +60,9 @@ class TestChat:
class TestAsyncChat:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -70,7 +70,9 @@ class TestContent:
class TestAsyncContent:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
@@ -60,7 +60,9 @@ class TestCancel:
class TestAsyncCancel:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -167,7 +167,9 @@ class TestJobs:
class TestAsyncJobs:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -130,7 +130,9 @@ class TestSpend:
class TestAsyncSpend:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
@@ -46,7 +46,9 @@ class TestGenerations:
class TestAsyncGenerations:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -54,7 +54,9 @@ class TestInfo:
class TestAsyncInfo:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -196,7 +196,9 @@ class TestUpdate:
class TestAsyncUpdate:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
@@ -60,7 +60,9 @@ class TestChat:
class TestAsyncChat:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
@@ -102,7 +102,9 @@ class TestDeployments:
class TestAsyncDeployments:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
@@ -87,7 +87,9 @@ class TestInfo:
class TestAsyncInfo:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
@@ -60,7 +60,9 @@ class TestInputItems:
class TestAsyncInputItems:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -122,7 +122,9 @@ class TestCallback:
class TestAsyncCallback:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -92,7 +92,9 @@ class TestModel:
class TestAsyncModel:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -46,7 +46,9 @@ class TestActive:
class TestAsyncActive:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -52,7 +52,9 @@ class TestAdd:
class TestAsyncAdd:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -228,7 +228,9 @@ class TestAnthropic:
class TestAsyncAnthropic:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -228,7 +228,9 @@ class TestAssemblyai:
class TestAsyncAssemblyai:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -116,7 +116,9 @@ class TestAssistants:
class TestAsyncAssistants:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -228,7 +228,9 @@ class TestAzure:
class TestAsyncAzure:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -341,7 +341,9 @@ class TestBatches:
class TestAsyncBatches:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -228,7 +228,9 @@ class TestBedrock:
class TestAsyncBedrock:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -248,7 +248,9 @@ class TestBudget:
class TestAsyncBudget:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -103,7 +103,9 @@ class TestCache:
class TestAsyncCache:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -46,7 +46,9 @@ class TestClient:
class TestAsyncClient:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -228,7 +228,9 @@ class TestCohere:
class TestAsyncCohere:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -54,7 +54,9 @@ class TestCompletions:
class TestAsyncCompletions:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -136,7 +136,9 @@ class TestCredentials:
class TestAsyncCredentials:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -295,7 +295,9 @@ class TestCustomer:
class TestAsyncCustomer:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -52,7 +52,9 @@ class TestDelete:
class TestAsyncDelete:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -54,7 +54,9 @@ class TestEmbeddings:
class TestAsyncEmbeddings:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -102,7 +102,9 @@ class TestEngines:
class TestAsyncEngines:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -228,7 +228,9 @@ class TestEuAssemblyai:
class TestAsyncEuAssemblyai:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -234,7 +234,9 @@ class TestFiles:
class TestAsyncFiles:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -228,7 +228,9 @@ class TestGemini:
class TestAsyncGemini:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -47,7 +47,9 @@ class TestGuardrails:
class TestAsyncGuardrails:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -172,7 +172,9 @@ class TestHealth:
class TestAsyncHealth:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -467,7 +467,9 @@ class TestKey:
class TestAsyncKey:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -228,7 +228,9 @@ class TestLangfuse:
class TestAsyncLangfuse:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -146,7 +146,9 @@ class TestModel:
class TestAsyncModel:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -54,7 +54,9 @@ class TestModelGroup:
class TestAsyncModelGroup:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -55,7 +55,9 @@ class TestModels:
class TestAsyncModels:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -46,7 +46,9 @@ class TestModerations:
class TestAsyncModerations:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -228,7 +228,9 @@ class TestOpenAI:
class TestAsyncOpenAI:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -326,7 +326,9 @@ class TestOrganization:
class TestAsyncOrganization:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -47,7 +47,9 @@ class TestProvider:
class TestAsyncProvider:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -102,7 +102,9 @@ class TestRerank:
class TestAsyncRerank:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -130,7 +130,9 @@ class TestResponses:
class TestAsyncResponses:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -46,7 +46,9 @@ class TestRoutes:
class TestAsyncRoutes:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -46,7 +46,9 @@ class TestSettings:
class TestAsyncSettings:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -137,7 +137,9 @@ class TestSpend:
class TestAsyncSpend:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -541,7 +541,9 @@ class TestTeam:
class TestAsyncTeam:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -46,7 +46,9 @@ class TestTest:
class TestAsyncTest:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -88,7 +88,9 @@ class TestThreads:
class TestAsyncThreads:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -260,7 +260,9 @@ class TestUser:
class TestAsyncUser:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -137,7 +137,9 @@ class TestUtils:
class TestAsyncUtils:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -228,7 +228,9 @@ class TestVertexAI:
class TestAsyncVertexAI:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -102,7 +102,9 @@ class TestMessages:
class TestAsyncMessages:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+3 -1
View File
@@ -60,7 +60,9 @@ class TestRuns:
class TestAsyncRuns:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@pytest.mark.skip()
@parametrize
+38 -5
View File
@@ -1,13 +1,17 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from __future__ import annotations
import os
import logging
from typing import TYPE_CHECKING, Iterator, AsyncIterator
import httpx
import pytest
from pytest_asyncio import is_async_test
from hanzoai import Hanzo, AsyncHanzo
from hanzoai import Hanzo, AsyncHanzo, DefaultAioHttpClient
from hanzoai._utils import is_dict
if TYPE_CHECKING:
from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage]
@@ -25,6 +29,19 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
for async_test in pytest_asyncio_tests:
async_test.add_marker(session_scope_marker, append=False)
# We skip tests that use both the aiohttp client and respx_mock as respx_mock
# doesn't support custom transports.
for item in items:
if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames:
continue
if not hasattr(item, "callspec"):
continue
async_client_param = item.callspec.params.get("async_client")
if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp":
item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock"))
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -43,9 +60,25 @@ def client(request: FixtureRequest) -> Iterator[Hanzo]:
@pytest.fixture(scope="session")
async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncHanzo]:
strict = getattr(request, "param", True)
if not isinstance(strict, bool):
raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
param = getattr(request, "param", True)
async with AsyncHanzo(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client:
# defaults
strict = True
http_client: None | httpx.AsyncClient = None
if isinstance(param, bool):
strict = param
elif is_dict(param):
strict = param.get("strict", True)
assert isinstance(strict, bool)
http_client_type = param.get("http_client", "httpx")
if http_client_type == "aiohttp":
http_client = DefaultAioHttpClient()
else:
raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict")
async with AsyncHanzo(
base_url=base_url, api_key=api_key, _strict_response_validation=strict, http_client=http_client
) as client:
yield client
+58 -13
View File
@@ -24,12 +24,13 @@ from pydantic import ValidationError
from hanzoai import Hanzo, AsyncHanzo, APIResponseValidationError
from hanzoai._types import Omit
from hanzoai._models import BaseModel, FinalRequestOptions
from hanzoai._constants import RAW_RESPONSE_HEADER
from hanzoai._exceptions import HanzoError, APIStatusError, APITimeoutError, APIResponseValidationError
from hanzoai._base_client import (
DEFAULT_TIMEOUT,
HTTPX_DEFAULT_TIMEOUT,
BaseClient,
DefaultHttpxClient,
DefaultAsyncHttpxClient,
make_request_options,
)
@@ -190,6 +191,7 @@ class TestHanzo:
copy_param = copy_signature.parameters.get(name)
assert copy_param is not None, f"copy() signature is missing the {name} param"
@pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12")
def test_copy_build_request(self) -> None:
options = FinalRequestOptions(method="get", url="/foo")
@@ -460,7 +462,7 @@ class TestHanzo:
def test_multipart_repeating_array(self, client: Hanzo) -> None:
request = client._build_request(
FinalRequestOptions.construct(
method="get",
method="post",
url="/foo",
headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
json_data={"array": ["foo", "bar"]},
@@ -717,22 +719,21 @@ class TestHanzo:
@mock.patch("hanzoai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Hanzo) -> None:
respx_mock.get("/").mock(side_effect=httpx.TimeoutException("Test timeout error"))
with pytest.raises(APITimeoutError):
self.client.get("/", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}})
client.with_streaming_response.get_home().__enter__()
assert _get_open_connections(self.client) == 0
@mock.patch("hanzoai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Hanzo) -> None:
respx_mock.get("/").mock(return_value=httpx.Response(500))
with pytest.raises(APIStatusError):
self.client.get("/", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}})
client.with_streaming_response.get_home().__enter__()
assert _get_open_connections(self.client) == 0
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@@ -810,6 +811,28 @@ class TestHanzo:
assert response.http_request.headers.get("x-stainless-retry-count") == "42"
def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Test that the proxy environment variables are set correctly
monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
client = DefaultHttpxClient()
mounts = tuple(client._mounts.items())
assert len(mounts) == 1
assert mounts[0][0].pattern == "https://"
@pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
def test_default_client_creation(self) -> None:
# Ensure that the client can be initialized without any exceptions
DefaultHttpxClient(
verify=True,
cert=None,
trust_env=True,
http1=True,
http2=False,
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
)
@pytest.mark.respx(base_url=base_url)
def test_follow_redirects(self, respx_mock: MockRouter) -> None:
# Test that the default follow_redirects=True allows following redirects
@@ -973,6 +996,7 @@ class TestAsyncHanzo:
copy_param = copy_signature.parameters.get(name)
assert copy_param is not None, f"copy() signature is missing the {name} param"
@pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12")
def test_copy_build_request(self) -> None:
options = FinalRequestOptions(method="get", url="/foo")
@@ -1245,7 +1269,7 @@ class TestAsyncHanzo:
def test_multipart_repeating_array(self, async_client: AsyncHanzo) -> None:
request = async_client._build_request(
FinalRequestOptions.construct(
method="get",
method="post",
url="/foo",
headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
json_data={"array": ["foo", "bar"]},
@@ -1516,22 +1540,21 @@ class TestAsyncHanzo:
@mock.patch("hanzoai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncHanzo) -> None:
respx_mock.get("/").mock(side_effect=httpx.TimeoutException("Test timeout error"))
with pytest.raises(APITimeoutError):
await self.client.get("/", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}})
await async_client.with_streaming_response.get_home().__aenter__()
assert _get_open_connections(self.client) == 0
@mock.patch("hanzoai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncHanzo) -> None:
respx_mock.get("/").mock(return_value=httpx.Response(500))
with pytest.raises(APIStatusError):
await self.client.get("/", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}})
await async_client.with_streaming_response.get_home().__aenter__()
assert _get_open_connections(self.client) == 0
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@@ -1659,6 +1682,28 @@ class TestAsyncHanzo:
time.sleep(0.1)
async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Test that the proxy environment variables are set correctly
monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
client = DefaultAsyncHttpxClient()
mounts = tuple(client._mounts.items())
assert len(mounts) == 1
assert mounts[0][0].pattern == "https://"
@pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
async def test_default_client_creation(self) -> None:
# Ensure that the client can be initialized without any exceptions
DefaultAsyncHttpxClient(
verify=True,
cert=None,
trust_env=True,
http1=True,
http2=False,
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
)
@pytest.mark.respx(base_url=base_url)
async def test_follow_redirects(self, respx_mock: MockRouter) -> None:
# Test that the default follow_redirects=True allows following redirects
+45
View File
@@ -889,3 +889,48 @@ def test_discriminated_union_case() -> None:
)
assert isinstance(m, ModelB)
def test_nested_discriminated_union() -> None:
class InnerType1(BaseModel):
type: Literal["type_1"]
class InnerModel(BaseModel):
inner_value: str
class InnerType2(BaseModel):
type: Literal["type_2"]
some_inner_model: InnerModel
class Type1(BaseModel):
base_type: Literal["base_type_1"]
value: Annotated[
Union[
InnerType1,
InnerType2,
],
PropertyInfo(discriminator="type"),
]
class Type2(BaseModel):
base_type: Literal["base_type_2"]
T = Annotated[
Union[
Type1,
Type2,
],
PropertyInfo(discriminator="base_type"),
]
model = construct_type(
type_=T,
value={
"base_type": "base_type_1",
"value": {
"type": "type_2",
},
},
)
assert isinstance(model, Type1)
assert isinstance(model.value, InnerType2)