diff --git a/.castiron.stats.yml b/.castiron.stats.yml index 530b190a4f..7f2789e84c 100644 --- a/.castiron.stats.yml +++ b/.castiron.stats.yml @@ -1,6 +1,6 @@ schema_version: 1 -generation_id: e1943695-9da0-4ac1-ae5a-cc763f5d90be +generation_id: 6f16b5a1-6b31-43ea-a1dd-a1a9fc37821e openapi_spec_hash: 1faf0319c407c6534ef7c381614afbb6 openapi_transformed_spec_hash: 53eff50caa9d18046e4ff0615bc7512d config_hash: 4617b0962f16d328804312ac81aac630 -codegen_sha: b97c76f867d26f99d0de01d6283fa6d511044141 +codegen_sha: 20f59b5cced5772bda4c6d6e3e19726d3805e4a1 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 5c2b1511f2..4191c8899b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.54.0" + ".": "3.0.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 851881a834..c2e2c7ae29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [3.0.0](https://github.com/openai/openai-python/compare/v2.54.0...v3.0.0) (2026-08-12) + + +### ⚠ BREAKING CHANGES + +* **api:** HTTPX2 is now the default HTTP client, and `httpx` is no longer installed automatically. Applications using custom HTTPX clients, transports, or configuration objects must migrate to their HTTPX2 equivalents or use the temporary, runtime-only legacy HTTPX escape hatch. See the [HTTPX2 migration guide](https://github.com/openai/openai-python/blob/main/httpx2.md). + +### Features + +* **api:** migrate to HTTPX2 ([#3594](https://github.com/openai/openai-python/pull/3594)) + ## [2.54.0](https://github.com/openai/openai-python/compare/v2.53.0...v2.54.0) (2026-08-11) diff --git a/README.md b/README.md index b5774f4c94..a8b39edfa7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ The OpenAI Python library provides convenient access to the OpenAI REST API from any Python 3.10+ application. The library includes type definitions for all request params and response fields, -and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). +and offers both synchronous and asynchronous clients powered by [HTTPX2](https://httpx2.pydantic.dev/). It is generated from our [OpenAPI specification](https://github.com/openai/openai-openapi) with [Stainless](https://stainlessapi.com/). @@ -244,9 +244,7 @@ Functionality between the synchronous and asynchronous clients is otherwise iden ### 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. - -The `aiohttp` backend requires Python 3.10 or later. +By default, the async client uses HTTPX2. For improved concurrency performance, you may also use `aiohttp` as the HTTPX2 transport. You can enable this by installing `aiohttp`: @@ -283,32 +281,9 @@ async def main() -> None: asyncio.run(main()) ``` -### Experimental HTTPX2 support - -To opt in to experimental HTTPX2 support, install the optional extra on Python 3.10 or later: - -```sh -pip install 'openai[httpx2]' -``` - -```python -from openai import OpenAI, AsyncOpenAI, DefaultHttpx2Client, DefaultAsyncHttpx2Client - -client = OpenAI(http_client=DefaultHttpx2Client()) -async_client = AsyncOpenAI(http_client=DefaultAsyncHttpx2Client()) -``` - -See [`examples/httpx2_client.py`](examples/httpx2_client.py) for a minimal runnable example. +### HTTPX2 migration -The module-level client can be configured in the same way: - -```python -import openai - -openai.http_client = openai.DefaultHttpx2Client() -``` - -Parsed API models are unchanged, but requests, raw and streaming responses, and transport-level exceptions may be HTTPX2 objects at runtime. Code that catches HTTPX exceptions or relies on HTTPX-specific mocks, transports, authentication, hooks, or instrumentation may need to be updated. Transport-facing type annotations may still describe HTTPX. +HTTPX2 is the default HTTP client. If you configure a custom HTTP client, transport, timeout, authentication handler, event hook, or request mock, see the [HTTPX2 migration guide](httpx2.md). ## Streaming responses @@ -636,7 +611,7 @@ try: ) except openai.APIConnectionError as e: print("The server could not be reached") - print(e.__cause__) # an underlying Exception, likely raised within httpx. + print(e.__cause__) # an underlying Exception, likely raised within HTTPX2. except openai.RateLimitError as e: print("A 429 status code was received; we should back off a bit.") except openai.APIStatusError as e: @@ -723,9 +698,10 @@ client.with_options(max_retries=5).chat.completions.create( ## Timeouts By default requests time out after 10 minutes. You can configure this with a `timeout` option, -which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: +which accepts a float or an [`httpx2.Timeout`](https://httpx2.pydantic.dev/) object: ```python +import httpx2 from openai import OpenAI # Configure the default for all requests: @@ -736,7 +712,7 @@ client = OpenAI( # More granular control: client = OpenAI( - timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), + timeout=httpx2.Timeout(60.0, read=5.0, write=10.0, connect=2.0), ) # Override per-request: @@ -849,11 +825,11 @@ To make requests to undocumented endpoints, you can make requests using `client. http verbs. Options on the client will be respected (such as retries) when making this request. ```py -import httpx +import httpx2 response = client.post( "/foo", - cast_to=httpx.Response, + cast_to=httpx2.Response, body={"my_param": True}, ) @@ -873,22 +849,18 @@ can also get all the extra fields on the Pydantic model as a dict with ### Configuring the HTTP client -You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including: - -- Support for [proxies](https://www.python-httpx.org/advanced/proxies/) -- Custom [transports](https://www.python-httpx.org/advanced/transports/) -- Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality +You can override the [HTTPX2 client](https://httpx2.pydantic.dev/) to customize proxies, transports, authentication, event hooks, and other advanced HTTP behavior. See the [HTTPX2 migration guide](httpx2.md) when updating an existing custom client. ```python -import httpx -from openai import OpenAI, DefaultHttpxClient +import httpx2 +from openai import OpenAI, DefaultHttpx2Client client = OpenAI( # Or use the `OPENAI_BASE_URL` env var base_url="http://my.test.server.example.com:8083/v1", - http_client=DefaultHttpxClient( + http_client=DefaultHttpx2Client( proxy="http://my.test.proxy.example.com", - transport=httpx.HTTPTransport(local_address="0.0.0.0"), + transport=httpx2.HTTPTransport(local_address="0.0.0.0"), ), ) ``` @@ -896,7 +868,7 @@ client = OpenAI( You can also customize the client on a per-request basis by using `with_options()`: ```python -client.with_options(http_client=DefaultHttpxClient(...)) +client.with_options(http_client=DefaultHttpx2Client(...)) ``` #### Mutual TLS @@ -913,7 +885,7 @@ and pass it through the custom HTTP client: import os import ssl -from openai import OpenAI, DefaultHttpxClient +from openai import OpenAI, DefaultHttpx2Client # Server trust is configured independently. Without `cafile`, this uses the # operating system's normal trusted certificate authorities. @@ -938,7 +910,7 @@ client = OpenAI( ), # A client certificate belongs to the HTTP client, not the base URL. # Disable redirects so it cannot follow a response to another origin. - http_client=DefaultHttpxClient( + http_client=DefaultHttpx2Client( verify=ssl_context, follow_redirects=False, ), @@ -951,7 +923,7 @@ The async configuration is equivalent: import os import ssl -from openai import AsyncOpenAI, DefaultAsyncHttpxClient +from openai import AsyncOpenAI, DefaultAsyncHttpx2Client ssl_context = ssl.create_default_context( cafile=os.environ.get("OPENAI_MTLS_CA_BUNDLE"), @@ -968,27 +940,7 @@ client = AsyncOpenAI( "OPENAI_BASE_URL", "https://mtls.api.openai.com/v1", ), - http_client=DefaultAsyncHttpxClient( - verify=ssl_context, - follow_redirects=False, - ), -) -``` - -Experimental HTTPX2 uses the same native `SSLContext`. Install the optional -extra with `pip install 'openai[httpx2]'`, then use `DefaultHttpx2Client` or -`DefaultAsyncHttpx2Client` in place of the corresponding HTTPX client above: - -```python -from openai import OpenAI, DefaultHttpx2Client - -client = OpenAI( - api_key=os.environ["OPENAI_API_KEY"], - base_url=os.environ.get( - "OPENAI_BASE_URL", - "https://mtls.api.openai.com/v1", - ), - http_client=DefaultHttpx2Client( + http_client=DefaultAsyncHttpx2Client( verify=ssl_context, follow_redirects=False, ), @@ -1001,7 +953,7 @@ See the complete [sync HTTPX2](examples/mtls_httpx2.py) and The certificate-bearing HTTP client is transport-wide. Dedicate it to the selected mTLS origin; do not reuse it for other services or pass it through `with_options()` with a different `base_url`. If redirects are required, add an -HTTPX request hook that rejects requests whose scheme, host, or port differs +HTTPX2 request hook that rejects requests whose scheme, host, or port differs from the configured mTLS origin before enabling `follow_redirects`. `SSLContext.load_cert_chain()` raises during setup for unreadable or malformed diff --git a/httpx2.md b/httpx2.md new file mode 100644 index 0000000000..044be5a533 --- /dev/null +++ b/httpx2.md @@ -0,0 +1,301 @@ +# Migrating to HTTPX2 + +The OpenAI Python SDK now uses [HTTPX2](https://httpx2.pydantic.dev/) for its +synchronous and asynchronous HTTP clients. HTTPX2 is installed automatically +with `openai`; the previous `httpx` package is not. This guide explains what +changes for applications that interact with the SDK's HTTP layer. + +## If you use the SDK's default HTTP client + +If you construct an `OpenAI` or +`AsyncOpenAI` client without providing `http_client`, your existing API calls, +parsed response models, streaming APIs, authentication, retries, and numeric +timeouts continue to work: + +```python +from openai import OpenAI + +client = OpenAI(timeout=30.0) +response = client.responses.create(model="gpt-5.5", input="Hello") +``` + +No HTTPX2 extra or separate installation is required: + +```sh +pip install openai +``` + +If your application imported `httpx` only because an earlier SDK installed it +transitively, add your own `httpx` dependency or migrate those imports to +`httpx2`. Installing the SDK no longer installs `httpx` for you. + +## TLS certificates and trust stores + +**HTTPX2 changes the default TLS trust store, including for applications that +use the SDK's default HTTP client.** HTTPX previously verified certificates +against the CA bundle provided by `certifi`. HTTPX2 instead uses the +operating-system trust store, and the SDK no longer installs `certifi`. + +This can break certificate verification in minimal container images without +system CA certificates, environments using corporate TLS-inspecting proxies, +and deployments that relied on a custom or modified `certifi` bundle. Install +the required CA certificates in the operating-system trust store, or configure +an explicit certificate bundle: + +```sh +export SSL_CERT_FILE=/path/to/ca-bundle.pem +``` + +Alternatively, configure a directory of trusted CA certificates: + +```sh +export SSL_CERT_DIR=/path/to/ca-directory +``` + +These environment variables are honored when `trust_env=True`, which is the +default. To control trust explicitly on a custom client, pass an +`ssl.SSLContext` through `verify`: + +```python +import ssl +from openai import OpenAI, DefaultHttpx2Client + +ssl_context = ssl.create_default_context(cafile="/path/to/ca-bundle.pem") +client = OpenAI(http_client=DefaultHttpx2Client(verify=ssl_context)) +``` + +Use `DefaultAsyncHttpx2Client(verify=ssl_context)` for the equivalent async +configuration. The SDK's aiohttp transport uses the same HTTPX2 TLS settings. + +## If you provide a custom HTTP client + +Use HTTPX2 clients and HTTPX2 configuration objects. The SDK provides helpers +that preserve its recommended timeout, connection-pool, and redirect defaults: + +```python +import httpx2 +from openai import OpenAI, AsyncOpenAI, DefaultHttpx2Client, DefaultAsyncHttpx2Client + +proxy_client = OpenAI(http_client=DefaultHttpx2Client(proxy="http://proxy.example.com:8080")) + +transport_client = OpenAI( + http_client=DefaultHttpx2Client( + transport=httpx2.HTTPTransport(local_address="0.0.0.0"), + timeout=httpx2.Timeout(30.0, connect=5.0), + ) +) + +async_client = AsyncOpenAI(http_client=DefaultAsyncHttpx2Client(timeout=httpx2.Timeout(30.0))) +``` + +Directly constructed `httpx2.Client` and `httpx2.AsyncClient` instances are +also supported. When you construct a client directly, its own HTTPX2 defaults +apply unless you configure them yourself. + +The existing `DefaultHttpxClient` and `DefaultAsyncHttpxClient` names continue +to work, but now construct HTTPX2 clients. Prefer `DefaultHttpx2Client` and +`DefaultAsyncHttpx2Client` when making the HTTP client family explicit. + +Module-level configuration follows the same rule: + +```python +import openai + +openai.http_client = openai.DefaultHttpx2Client() +``` + +## Timeouts, URLs, transports, and connection settings + +Replace HTTPX-specific objects with the corresponding HTTPX2 objects: + +| Previous object | HTTPX2 object | +| --- | --- | +| `httpx.Client` | `httpx2.Client` | +| `httpx.AsyncClient` | `httpx2.AsyncClient` | +| `httpx.Timeout` | `httpx2.Timeout` | +| `httpx.URL` | `httpx2.URL` | +| `httpx.Limits` | `httpx2.Limits` | +| `httpx.HTTPTransport` | `httpx2.HTTPTransport` | +| `httpx.AsyncHTTPTransport` | `httpx2.AsyncHTTPTransport` | +| `httpx.MockTransport` | `httpx2.MockTransport` | + +For example, a granular SDK timeout becomes: + +```python +import httpx2 +from openai import OpenAI + +client = OpenAI(timeout=httpx2.Timeout(60.0, connect=5.0, read=20.0)) +``` + +Numeric timeout values do not change. Existing string URLs do not change. +Custom transport subclasses, mounted transports, proxy integrations, and +connection-pool instrumentation must target HTTPX2's transport interfaces. + +## Authentication and event hooks + +Authentication handlers and hooks receive HTTPX2 request and response objects. +Update custom auth classes and annotations accordingly: + +```python +import httpx2 +from openai import OpenAI, DefaultHttpx2Client + + +def log_request(request: httpx2.Request) -> None: + print(request.method, request.url) + + +client = OpenAI(http_client=DefaultHttpx2Client(event_hooks={"request": [log_request]})) +``` + +If you subclass an HTTP authentication or transport interface, subclass the +matching `httpx2` class. Third-party instrumentation, tracing middleware, and +auth integrations must explicitly support HTTPX2. + +## Raw responses, streaming, and exceptions + +Parsed SDK response models are unchanged. When using a native HTTPX2 client, +transport-facing objects belong to HTTPX2: + +```python +import httpx2 +from openai import OpenAI + +client = OpenAI() +response = client.models.with_raw_response.list() + +assert isinstance(response.http_response, httpx2.Response) +assert isinstance(response.http_request, httpx2.Request) +``` + +With a native client, use `cast_to=httpx2.Response` when requesting an unparsed +HTTP response. Streaming response wrappers also expose HTTPX2 response objects. +Application code should usually catch SDK exceptions such as +`openai.APITimeoutError` and `openai.APIConnectionError`; with a native client, +an exception's underlying transport cause is an HTTPX2 exception. + +These type guarantees apply only to native HTTPX2 clients. An injected legacy +HTTPX client produces `httpx.Request`, `httpx.Response`, and HTTPX transport +exceptions instead, even if `cast_to=httpx2.Response` is supplied. + +## aiohttp + +The supported aiohttp extra uses an HTTPX2-native transport. It does not +install legacy HTTPX or the external `httpx-aiohttp` adapter: + +```sh +pip install 'openai[aiohttp]' +``` + +```python +from openai import AsyncOpenAI, DefaultAioHttpClient + +client = AsyncOpenAI(http_client=DefaultAioHttpClient()) +``` + +`DefaultAioHttpClient()` is an `httpx2.AsyncClient`. Applications using this +helper do not need to construct or import the transport directly. + +## Request mocking and tests + +Mocks must intercept HTTPX2 requests and return HTTPX2 responses. For example: + +```python +import httpx2 +from openai import OpenAI + + +def handler(request: httpx2.Request) -> httpx2.Response: + return httpx2.Response( + 200, + request=request, + json={"object": "list", "data": []}, + ) + + +client = OpenAI(http_client=httpx2.Client(transport=httpx2.MockTransport(handler))) +assert client.models.list().data == [] +``` + +If your test suite uses RESPX, update to an HTTPX2-compatible RESPX version or +fork. A RESPX version that patches only legacy HTTPX cannot intercept the SDK's +default HTTPX2 client. If you cannot migrate that integration immediately, the +temporary legacy-client escape hatch below lets existing HTTPX-only RESPX +setups continue to work while you migrate. + +## Temporary escape hatch: a legacy HTTPX client + +Applications that depend on an HTTPX-only transport, integration, or mocking +library can explicitly install legacy HTTPX and inject a legacy client: + +```sh +pip install openai httpx +``` + +**Legacy HTTPX support is runtime-only.** The SDK's public type annotations +accept HTTPX2 clients, so passing a legacy client directly fails static type +checking in mypy, Pyright, and similar tools. Use `cast(Any, ...)` or a +targeted type-ignore when deliberately choosing this compatibility path: + +```python +from typing import Any, cast + +import httpx +from openai import OpenAI + +client = OpenAI(http_client=cast(Any, httpx.Client())) +``` + +The asynchronous form requires the same workaround: + +```python +from typing import Any, cast + +import httpx +from openai import AsyncOpenAI + +client = AsyncOpenAI(http_client=cast(Any, httpx.AsyncClient())) +``` + +Legacy clients preserve the HTTPX request, response, and exception families. +Request raw responses as `httpx.Response`, using the same type-checking +workaround for the legacy response class: + +```python +from typing import Any, cast + +import httpx +from openai import OpenAI + +client = OpenAI(http_client=cast(Any, httpx.Client())) +response = client.get("/models", cast_to=cast(Any, httpx.Response)) + +assert isinstance(response, httpx.Response) +``` + +Passing `cast_to=httpx2.Response` does not convert a legacy HTTPX response into +an HTTPX2 response. Install and maintain the legacy dependency yourself. +Legacy HTTPX support is provided as a migration aid and may be discontinued. + +### Existing legacy aiohttp adapters + +If you must retain an existing `httpx-aiohttp` integration, install it +explicitly and inject its legacy client: + +```sh +pip install openai httpx-aiohttp +``` + +```python +from typing import Any, cast + +from httpx_aiohttp import HttpxAiohttpClient +from openai import AsyncOpenAI + +client = AsyncOpenAI(http_client=cast(Any, HttpxAiohttpClient())) +``` + +This path is covered by dedicated compatibility tests, including a real +request through the aiohttp transport, but remains a temporary escape hatch. +Prefer `openai[aiohttp]` and `DefaultAioHttpClient()` for new code. diff --git a/pyproject.toml b/pyproject.toml index aa51d75238..4a38bd11e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "openai" -version = "2.54.0" +version = "3.0.0" description = "The official Python library for the openai API" dynamic = ["readme"] license = "Apache-2.0" @@ -9,10 +9,10 @@ authors = [ ] dependencies = [ - "httpx>=0.23.0, <1", + "httpx2>=2.7.0, <3", "pydantic>=1.9.0, <3", "typing-extensions>=4.14, <5", - "anyio>=3.5.0, <5", + "anyio>=4.10.0, <5", "distro>=1.7.0, <2", "sniffio", "tqdm > 4", @@ -44,12 +44,6 @@ Repository = "https://github.com/openai/openai-python" [project.optional-dependencies] aiohttp = [ "aiohttp>=3.14.1", - "httpx_aiohttp>=0.1.9", -] -httpx2 = [ - "httpx>=0.25.1, <1", - "httpx2>=2.7.0, <3", - "anyio>=4.10.0, <5", ] realtime = ["websockets >= 13, < 16"] datalib = ["numpy >= 1", "pandas >= 1.2.3", "pandas-stubs >= 1.1.0.11"] @@ -64,7 +58,6 @@ managed = true dev-dependencies = [ "pyright==1.1.399", "mypy==1.17", - "respx", "pytest", "pytest-asyncio", "jsonschema>=4.23.0", @@ -177,6 +170,8 @@ exclude = [ ".venv", ".nox", ".git", + "tests/respx2", + "src/openai/_vendor", # uses inline `uv` script dependencies # which means it can't be type checked @@ -205,6 +200,7 @@ show_error_codes = true # which means it can't be type checked exclude = [ 'src/openai/_files.py', + 'src/openai/_vendor/.*', '_dev/.*.py', 'tests/.*', 'src/openai/_utils/_logs.py', @@ -252,6 +248,7 @@ ignore_errors = true ignore_missing_imports = true [tool.ruff] +extend-exclude = ["tests/respx2", "src/openai/_vendor"] line-length = 120 output-format = "grouped" target-version = "py310" diff --git a/requirements-dev.lock b/requirements-dev.lock index 40db36e36c..efb71cd787 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -13,14 +13,12 @@ aiohappyeyeballs==2.7.1 # via aiohttp aiohttp==3.14.1 - # via httpx-aiohttp # via openai aiosignal==1.4.0 # via aiohttp annotated-types==0.7.0 # via pydantic anyio==4.12.1 - # via httpx # via httpx2 # via openai argcomplete==3.6.3 @@ -44,8 +42,6 @@ backports-asyncio-runner==1.2.0 ; python_full_version < '3.11' botocore==1.42.97 # via openai certifi==2026.1.4 - # via httpcore - # via httpx # via requests cffi==2.0.0 # via cryptography @@ -86,25 +82,15 @@ frozenlist==1.8.0 # via aiosignal griffe==1.14.0 h11==0.16.0 - # via httpcore # via httpcore2 -httpcore==1.0.9 - # via httpx httpcore2==2.7.0 # via httpx2 -httpx==0.28.1 - # via httpx-aiohttp - # via openai - # via respx -httpx-aiohttp==0.1.12 - # via openai httpx2==2.7.0 # via openai humanize==4.13.0 # via nox idna==3.18 # via anyio - # via httpx # via httpx2 # via requests # via trio @@ -196,7 +182,6 @@ referencing==0.36.2 requests==2.32.5 # via azure-core # via msal -respx==0.22.0 rich==14.2.0 # via inline-snapshot rpds-py==0.27.1 diff --git a/requirements.lock b/requirements.lock index 7ec347011b..0323bd6066 100644 --- a/requirements.lock +++ b/requirements.lock @@ -13,14 +13,12 @@ aiohappyeyeballs==2.7.1 # via aiohttp aiohttp==3.14.1 - # via httpx-aiohttp # via openai aiosignal==1.4.0 # via aiohttp annotated-types==0.7.0 # via pydantic anyio==4.12.1 - # via httpx # via httpx2 # via openai async-timeout==5.0.1 ; python_full_version < '3.11' @@ -29,9 +27,6 @@ attrs==26.1.0 # via aiohttp botocore==1.42.97 # via openai -certifi==2026.1.4 - # via httpcore - # via httpx cffi==2.0.0 # via sounddevice colorama==0.4.6 ; sys_platform == 'win32' @@ -44,22 +39,13 @@ frozenlist==1.8.0 # via aiohttp # via aiosignal h11==0.16.0 - # via httpcore # via httpcore2 -httpcore==1.0.9 - # via httpx httpcore2==2.7.0 # via httpx2 -httpx==0.28.1 - # via httpx-aiohttp - # via openai -httpx-aiohttp==0.1.12 - # via openai httpx2==2.7.0 # via openai idna==3.18 # via anyio - # via httpx # via httpx2 # via yarl jiter==0.12.0 diff --git a/scripts/check-python-version-policy.py b/scripts/check-python-version-policy.py index 16217267c0..ca74199485 100644 --- a/scripts/check-python-version-policy.py +++ b/scripts/check-python-version-policy.py @@ -8,10 +8,8 @@ MINIMUM = SUPPORTED[0] CURRENT_STABLE = SUPPORTED[-1] PRERELEASE = "3.15" -UNMARKED_OPTIONAL_DEPENDENCIES = ( +UNMARKED_DEPENDENCIES = ( "aiohttp>=3.14.1", - "httpx_aiohttp>=0.1.9", - "httpx>=0.25.1, <1", "httpx2>=2.7.0, <3", "anyio>=4.10.0, <5", "botocore>=1.40.0,<2", @@ -124,7 +122,7 @@ def main() -> None: ) project_metadata = pyproject.split("[tool.rye]", 1)[0] - for requirement in UNMARKED_OPTIONAL_DEPENDENCIES: + for requirement in UNMARKED_DEPENDENCIES: require( f'"{requirement}"' in project_metadata, f"Package metadata does not contain the unmarked requirement {requirement!r}", diff --git a/scripts/utils/validate-bedrock-wheel.py b/scripts/utils/validate-bedrock-wheel.py index ea4abdc74e..e0bf4f5890 100644 --- a/scripts/utils/validate-bedrock-wheel.py +++ b/scripts/utils/validate-bedrock-wheel.py @@ -14,7 +14,7 @@ import importlib.abc from pathlib import Path -import httpx +import httpx2 class BlockBotocore(importlib.abc.MetaPathFinder): @@ -39,22 +39,22 @@ def find_spec(self, fullname, path=None, target=None): def handler(request): requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) -http_client = httpx.Client(transport=httpx.MockTransport(handler), trust_env=False) +http_client = httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False) with OpenAI( provider=bedrock(region="us-east-1", api_key="bearer-token"), http_client=http_client, ) as client: - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) assert requests[0].headers["Authorization"] == "Bearer bearer-token" assert not any(name == "botocore" or name.startswith("botocore.") for name in sys.modules) sys.meta_path.remove(blocker) requests.clear() -http_client = httpx.Client(transport=httpx.MockTransport(handler), trust_env=False) +http_client = httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False) with OpenAI( provider=bedrock( region="us-east-1", @@ -64,7 +64,7 @@ def handler(request): ), http_client=http_client, ) as client: - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) assert "Credential=fixture-access-key/" in requests[0].headers["Authorization"] assert requests[0].headers["X-Amz-Security-Token"] == "fixture-session-token" diff --git a/scripts/utils/validate-httpx2-wheel.py b/scripts/utils/validate-httpx2-wheel.py index e8b12147c9..8e0642efba 100644 --- a/scripts/utils/validate-httpx2-wheel.py +++ b/scripts/utils/validate-httpx2-wheel.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import re import sys import email import zipfile @@ -11,12 +12,20 @@ ROOT = Path(__file__).resolve().parents[2] BASE_TEST = ROOT / "tests/test_httpx2_base.py" HTTPX2_TEST = ROOT / "tests/test_httpx2.py" +LEGACY_TEST = ROOT / "tests/test_httpx_compat.py" def venv_python(environment_path: Path) -> Path: return environment_path / ("Scripts/python.exe" if sys.platform == "win32" else "bin/python") +def requirement_name(requirement: str) -> str: + match = re.match(r"[A-Za-z0-9_.-]+", requirement) + if match is None: + raise RuntimeError(f"Cannot determine the package name for {requirement!r}") + return match.group().replace("_", "-").lower() + + def validate_metadata(wheel: Path) -> None: with zipfile.ZipFile(wheel) as archive: metadata_names = [name for name in archive.namelist() if name.endswith(".dist-info/METADATA")] @@ -24,39 +33,40 @@ def validate_metadata(wheel: Path) -> None: raise RuntimeError(f"Expected exactly one METADATA file in {wheel}, found: {metadata_names}") metadata = email.message_from_bytes(archive.read(metadata_names[0])) + for name in ("LICENSE", "README.md", "FORK.md"): + if f"openai/_vendor/httpx_aiohttp/{name}" not in archive.namelist(): + raise RuntimeError(f"The wheel omitted the vendored aiohttp adapter's {name}") + requirements = metadata.get_all("Requires-Dist", []) base = [value for value in requirements if "extra ==" not in value] - httpx2 = [value for value in requirements if "extra == 'httpx2'" in value] aiohttp = [value for value in requirements if "extra == 'aiohttp'" in value] + extras = set(metadata.get_all("Provides-Extra", [])) if metadata["Requires-Python"] != ">=3.10": raise RuntimeError(f"Expected Python >=3.10, found: {metadata['Requires-Python']}") - if not any(value.startswith("httpx<1,>=0.23.0") for value in base): - raise RuntimeError(f"Expected the base wheel to require HTTPX >=0.23.0,<1: {base}") - if not any(value.startswith("anyio<5,>=3.5.0") for value in base): - raise RuntimeError(f"Expected the base wheel to require AnyIO >=3.5.0,<5: {base}") - if any(value.startswith("httpx2") for value in base): - raise RuntimeError(f"HTTPX2 leaked into the base wheel requirements: {base}") - - for expected in ("httpx<1,>=0.25.1", "httpx2<3,>=2.7.0", "anyio<5,>=4.10.0"): - if not any(value.startswith(expected) for value in httpx2): - raise RuntimeError(f"Expected the HTTPX2 extra to require {expected}: {httpx2}") - if any("python_version" in value for value in httpx2): - raise RuntimeError(f"HTTPX2 requirements have redundant Python markers: {httpx2}") - + for expected in ("httpx2<3,>=2.7.0", "anyio<5,>=4.10.0"): + if not any(value.startswith(expected) for value in base): + raise RuntimeError(f"Expected the base wheel to require {expected}: {base}") + if any(requirement_name(value) == "httpx" for value in requirements): + raise RuntimeError(f"Legacy HTTPX must not be installed by any SDK extra: {requirements}") + if any(requirement_name(value) == "httpx-aiohttp" for value in requirements): + raise RuntimeError(f"The aiohttp extra must not install the legacy adapter package: {requirements}") + if {"httpx", "httpx2"} & extras: + raise RuntimeError(f"HTTP client selection must not require or expose an SDK extra: {extras}") if not any(value.startswith("aiohttp>=3.14.1") for value in aiohttp): - raise RuntimeError(f"Expected the unchanged aiohttp requirement: {aiohttp}") - if not any(value.startswith("httpx-aiohttp>=0.1.9") for value in aiohttp): - raise RuntimeError(f"Expected the unchanged httpx-aiohttp requirement: {aiohttp}") + raise RuntimeError(f"Expected the aiohttp extra to require a patched aiohttp release: {aiohttp}") -def run_case(wheel: Path, *, extra: str | None, tests: list[Path], dependencies: list[str]) -> None: +def run_case( + wheel: Path, *, extra: str | None, tests: list[Path], dependencies: list[str], legacy: bool = False +) -> None: with tempfile.TemporaryDirectory(prefix="openai-httpx2-wheel-") as directory: environment_path = Path(directory) / "venv" subprocess.run([sys.executable, "-m", "venv", str(environment_path)], check=True) python = venv_python(environment_path) requirement = str(wheel.resolve()) if extra is None else f"{wheel.resolve()}[{extra}]" environment = os.environ.copy() + environment.pop("PYTHONPATH", None) environment.setdefault("PIP_DISABLE_CLIENT_CERTIFICATE", "1") subprocess.run( [str(python), "-m", "pip", "install", "--quiet", requirement, *dependencies], @@ -64,9 +74,20 @@ def run_case(wheel: Path, *, extra: str | None, tests: list[Path], dependencies: env=environment, check=True, ) + + if not legacy: + subprocess.run( + [str(python), "-c", "import importlib.util; assert importlib.util.find_spec('httpx') is None"], + cwd=directory, + env=environment, + check=True, + ) + test_environment = environment.copy() for name in ("ALL_PROXY", "HTTPS_PROXY", "HTTP_PROXY", "all_proxy", "https_proxy", "http_proxy"): test_environment.pop(name, None) + if legacy: + test_environment["OPENAI_TEST_LEGACY_HTTPX"] = "1" subprocess.run( [str(python), "-m", "pytest", "-o", "addopts=", *(str(test) for test in tests)], cwd=directory, @@ -75,34 +96,6 @@ def run_case(wheel: Path, *, extra: str | None, tests: list[Path], dependencies: ) -def assert_incompatible_pin_fails(wheel: Path) -> None: - with tempfile.TemporaryDirectory(prefix="openai-httpx2-conflict-") as directory: - environment_path = Path(directory) / "venv" - subprocess.run([sys.executable, "-m", "venv", str(environment_path)], check=True) - python = venv_python(environment_path) - result = subprocess.run( - [ - str(python), - "-m", - "pip", - "install", - "--no-input", - f"{wheel.resolve()}[httpx2]", - "httpx==0.25.0", - ], - cwd=directory, - env=os.environ.copy(), - capture_output=True, - text=True, - check=False, - ) - if result.returncode == 0: - raise RuntimeError("Expected openai[httpx2] with httpx==0.25.0 to fail dependency resolution") - output = result.stdout + result.stderr - if "ResolutionImpossible" not in output and "conflicting dependencies" not in output: - raise RuntimeError(f"The incompatible resolution failed for an unexpected reason:\n{output}") - - def main() -> None: wheels = list((ROOT / "dist").glob("*.whl")) if len(wheels) != 1: @@ -110,32 +103,17 @@ def main() -> None: wheel = wheels[0] validate_metadata(wheel) - common = ["pytest==8.4.1", "pytest-asyncio==1.1.0", "respx==0.22.0"] - run_case(wheel, extra=None, tests=[BASE_TEST], dependencies=common) - if sys.version_info[:2] == (3, 10): - run_case( - wheel, - extra=None, - tests=[BASE_TEST], - dependencies=[ - "pytest==8.4.1", - "pytest-asyncio==1.1.0", - "respx==0.20.2", - "httpx==0.23.0", - "anyio==3.5.0", - ], - ) - + common = ["pytest==8.4.1", "pytest-asyncio==1.1.0"] + run_case(wheel, extra=None, tests=[BASE_TEST, HTTPX2_TEST], dependencies=common) run_case(wheel, extra="aiohttp", tests=[BASE_TEST], dependencies=common) - run_case(wheel, extra="httpx2", tests=[BASE_TEST, HTTPX2_TEST], dependencies=common) run_case( wheel, - extra="httpx2", - tests=[BASE_TEST, HTTPX2_TEST], - dependencies=[*common, "httpx==0.25.1", "anyio==4.10.0", "pydantic<2", "botocore==1.42.97"], + extra=None, + tests=[LEGACY_TEST], + dependencies=[*common, "httpx-aiohttp>=0.2.0,<0.3"], + legacy=True, ) - assert_incompatible_pin_fails(wheel) - print("Validated base, aiohttp, native HTTPX2, supported floors, Pydantic modes, and resolver conflicts") + print("Validated HTTPX2-only base and aiohttp installs plus isolated legacy HTTPX/aiohttp compatibility") if __name__ == "__main__": diff --git a/src/openai/__init__.py b/src/openai/__init__.py index d433ac4318..5a8621cf4c 100644 --- a/src/openai/__init__.py +++ b/src/openai/__init__.py @@ -130,7 +130,7 @@ import typing as _t import typing_extensions as _te -import httpx as _httpx +import httpx2 as _httpx from ._base_client import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES diff --git a/src/openai/_base_client.py b/src/openai/_base_client.py index 10d7b9f7ca..b6e2f2839d 100644 --- a/src/openai/_base_client.py +++ b/src/openai/_base_client.py @@ -34,10 +34,10 @@ from typing_extensions import Unpack, Literal, override, get_origin import anyio -import httpx import distro +import httpx2 import pydantic -from httpx import URL +from httpx2 import URL from pydantic import PrivateAttr from . import _exceptions @@ -67,12 +67,17 @@ from ._httpx2 import ( status_exceptions, timeout_exceptions, + http_response_types, normalize_httpx_url, is_httpx2_sync_client, normalize_httpx2_auth, is_httpx2_async_client, normalize_httpx_timeout, normalize_httpx2_timeout, + is_legacy_httpx_sync_client, + normalize_legacy_httpx_auth, + is_legacy_httpx_async_client, + normalize_legacy_httpx_timeout, ) from ._models import GenericModel, SecurityOptions, FinalRequestOptions, validate_type, construct_type from ._response import ( @@ -117,14 +122,14 @@ _AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any]) if TYPE_CHECKING: - from httpx._config import ( + from httpx2._config import ( DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage] ) HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG else: try: - from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT + from httpx2._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT except ImportError: # taken from https://github.com/encode/httpx/blob/3ba5fe0d7ac70222590e759c31442b1cab263791/httpx/_config.py#L366 HTTPX_DEFAULT_TIMEOUT = Timeout(5.0) @@ -207,9 +212,9 @@ def next_page_info(self) -> Optional[PageInfo]: ... def _get_page_items(self) -> Iterable[_T]: # type: ignore[empty-body] ... - def _params_from_url(self, url: URL) -> httpx.QueryParams: + def _params_from_url(self, url: URL) -> httpx2.QueryParams: # TODO: do we have to preprocess params here? - return httpx.QueryParams(cast(Any, self._options.params)).merge(url.params) + return httpx2.QueryParams(cast(Any, self._options.params)).merge(url.params) def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: options = model_copy(self._options) @@ -371,7 +376,7 @@ async def get_next_page(self: AsyncPageT) -> AsyncPageT: return await self._client._request_api_list(self._model, page=self.__class__, options=options) -_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient]) +_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx2.Client, httpx2.AsyncClient]) _DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]]) @@ -418,7 +423,7 @@ def _enforce_trailing_slash(self, url: URL) -> URL: def _make_status_error_from_response( self, - response: httpx.Response, + response: httpx2.Response, ) -> APIStatusError: if response.is_closed and not response.is_stream_consumed: # We can't read the response body as it has been closed @@ -443,7 +448,7 @@ def _make_status_error( err_msg: str, *, body: object, - response: httpx.Response, + response: httpx2.Response, ) -> _exceptions.APIStatusError: raise NotImplementedError() @@ -462,16 +467,16 @@ def _auth_query( def _custom_auth( self, security: SecurityOptions, # noqa: ARG002 - ) -> httpx.Auth | None: + ) -> httpx2.Auth | None: return None - def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers: + def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx2.Headers: custom_headers = options.headers or {} headers_dict = _merge_mappings({**self._auth_headers(options.security), **self.default_headers}, custom_headers) self._validate_headers(headers_dict, custom_headers) # headers are case-insensitive while dictionaries are not. - headers = httpx.Headers(headers_dict) + headers = httpx2.Headers(headers_dict) idempotency_header = self._idempotency_header if idempotency_header and options.idempotency_key and idempotency_header not in headers: @@ -514,7 +519,7 @@ def _build_request( options: FinalRequestOptions, *, retries_taken: int = 0, - ) -> httpx.Request: + ) -> httpx2.Request: if log.isEnabledFor(logging.DEBUG): log.debug( "Request options: %s", @@ -604,11 +609,11 @@ def _build_request( kwargs.pop("data", None) timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout - request_url: str | URL = prepared_url - request_headers: httpx.Headers | list[tuple[str, str]] = headers - if is_httpx2_sync_client(self._client) or is_httpx2_async_client(self._client): - request_url = str(prepared_url) - request_headers = list(headers.multi_items()) + request_url = str(prepared_url) + request_headers = list(headers.multi_items()) + if is_legacy_httpx_sync_client(self._client) or is_legacy_httpx_async_client(self._client): + timeout = normalize_legacy_httpx_timeout(timeout) + else: timeout = normalize_httpx2_timeout(timeout) # TODO: report this error to httpx @@ -672,7 +677,7 @@ def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalReques return cast_to - def _should_stream_response_body(self, request: httpx.Request) -> bool: + def _should_stream_response_body(self, request: httpx2.Request) -> bool: return request.headers.get(RAW_RESPONSE_HEADER) == "stream" # type: ignore[no-any-return] def _process_response_data( @@ -680,7 +685,7 @@ def _process_response_data( *, data: object, cast_to: type[ResponseT], - response: httpx.Response, + response: httpx2.Response, ) -> ResponseT: if data is None: return cast(ResponseT, None) @@ -704,7 +709,7 @@ def qs(self) -> Querystring: return Querystring() @property - def custom_auth(self) -> httpx.Auth | None: + def custom_auth(self) -> httpx2.Auth | None: return None @property @@ -756,7 +761,7 @@ def platform_headers(self) -> Dict[str, str]: # https://github.com/python/cpython/issues/88476 return platform_headers(self._version, platform=self._platform) - def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None: + def _parse_retry_after_header(self, response_headers: Optional[httpx2.Headers] = None) -> float | None: """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified. About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After @@ -798,7 +803,7 @@ def _calculate_retry_timeout( self, remaining_retries: int, options: FinalRequestOptions, - response_headers: Optional[httpx.Headers] = None, + response_headers: Optional[httpx2.Headers] = None, ) -> float: max_retries = options.get_max_retries(self.max_retries) @@ -818,7 +823,7 @@ def _calculate_retry_timeout( timeout = sleep_seconds * jitter return timeout if timeout >= 0 else 0 - def _should_retry(self, response: httpx.Response) -> bool: + def _should_retry(self, response: httpx2.Response) -> bool: retry_after = self._parse_retry_after_header(response.headers) if retry_after is not None and math.isfinite(retry_after) and retry_after > MAX_RETRY_AFTER_DELAY: log.debug( @@ -866,7 +871,7 @@ def _idempotency_key(self) -> str: return f"stainless-python-retry-{uuid.uuid4()}" -class _DefaultHttpxClient(httpx.Client): +class _DefaultHttpxClient(httpx2.Client): def __init__(self, **kwargs: Any) -> None: kwargs.setdefault("timeout", DEFAULT_TIMEOUT) kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) @@ -875,12 +880,12 @@ def __init__(self, **kwargs: Any) -> None: if TYPE_CHECKING: - DefaultHttpxClient = httpx.Client - """An alias to `httpx.Client` that provides the same defaults that this SDK + DefaultHttpxClient = httpx2.Client + """An alias to `httpx2.Client` that provides the same defaults that this SDK uses internally. This is useful because overriding the `http_client` with your own instance of - `httpx.Client` will result in httpx's defaults being used, not ours. + `httpx2.Client` will result in HTTPX2's defaults being used, not ours. """ else: DefaultHttpxClient = _DefaultHttpxClient @@ -897,8 +902,8 @@ def __del__(self) -> None: pass -class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]): - _client: httpx.Client +class SyncAPIClient(BaseClient[httpx2.Client, Stream[Any]]): + _client: httpx2.Client _default_stream_cls: type[Stream[Any]] | None = None def __init__( @@ -908,7 +913,7 @@ def __init__( base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = not_given, - http_client: httpx.Client | None = None, + http_client: httpx2.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, _strict_response_validation: bool, @@ -930,7 +935,7 @@ def __init__( if ( http_client is not None and not is_httpx2_sync_client(http_client) - and not isinstance(http_client, httpx.Client) # pyright: ignore[reportUnnecessaryIsInstance] + and not is_legacy_httpx_sync_client(http_client) ): raise TypeError( "Invalid `http_client` argument; Expected an instance of `httpx.Client` or `httpx2.Client` " @@ -986,7 +991,7 @@ def _prepare_options( def _prepare_request( self, - request: httpx.Request, # noqa: ARG002 + request: httpx2.Request, # noqa: ARG002 ) -> None: """This method is used as a callback for mutating the `Request` object after it has been constructed. @@ -997,11 +1002,11 @@ def _prepare_request( def _send_request( self, - request: httpx.Request, + request: httpx2.Request, *, stream: bool, **kwargs: Unpack[HttpxSendArgs], - ) -> httpx.Response: + ) -> httpx2.Response: return self._client.send(request, stream=stream, **kwargs) @overload @@ -1051,7 +1056,7 @@ def request( # ensure the idempotency key is reused between requests input_options.idempotency_key = self._idempotency_key() - response: httpx.Response | None = None + response: httpx2.Response | None = None max_retries = input_options.get_max_retries(self.max_retries) retries_taken = 0 @@ -1067,7 +1072,9 @@ def request( custom_auth = self._custom_auth(options.security) if custom_auth is not None: kwargs["auth"] = ( - normalize_httpx2_auth(custom_auth) if is_httpx2_sync_client(self._client) else custom_auth + normalize_httpx2_auth(custom_auth) + if is_httpx2_sync_client(self._client) + else normalize_legacy_httpx_auth(custom_auth) ) if options.follow_redirects is not None: @@ -1160,7 +1167,7 @@ def request( ) def _sleep_for_retry( - self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx2.Response | None ) -> None: remaining_retries = max_retries - retries_taken if remaining_retries == 1: @@ -1178,7 +1185,7 @@ def _process_response( *, cast_to: Type[ResponseT], options: FinalRequestOptions, - response: httpx.Response, + response: httpx2.Response, stream: bool, stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, retries_taken: int = 0, @@ -1224,7 +1231,7 @@ def _process_response( ), ) - if cast_to == httpx.Response: + if cast_to in http_response_types(): return cast(ResponseT, response) api_response = APIResponse( @@ -1461,7 +1468,7 @@ def get_api_list( return self._request_api_list(model, page, opts) -class _DefaultAsyncHttpxClient(httpx.AsyncClient): +class _DefaultAsyncHttpxClient(httpx2.AsyncClient): def __init__(self, **kwargs: Any) -> None: kwargs.setdefault("timeout", DEFAULT_TIMEOUT) kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) @@ -1469,43 +1476,40 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) -if sys.version_info < (3, 10): +_DefaultAioHttpClient: type[httpx2.AsyncClient] + +try: + from ._vendor.httpx_aiohttp import Httpx2AiohttpClient +except ImportError: - class _DefaultAioHttpClient(httpx.AsyncClient): + class _MissingAioHttpClient(httpx2.AsyncClient): def __init__(self, **_kwargs: Any) -> None: - raise RuntimeError("The aiohttp client requires Python 3.10 or later") -else: - try: - import httpx_aiohttp - except ImportError: + raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") - 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: + _DefaultAioHttpClient = _MissingAioHttpClient +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) + class _InstalledAioHttpClient(Httpx2AiohttpClient): + 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) - super().__init__(**kwargs) + _DefaultAioHttpClient = _InstalledAioHttpClient if TYPE_CHECKING: - DefaultAsyncHttpxClient = httpx.AsyncClient - """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK + DefaultAsyncHttpxClient = httpx2.AsyncClient + """An alias to `httpx2.AsyncClient` that provides the same defaults that this SDK uses internally. 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. + `httpx2.AsyncClient` will result in HTTPX2's defaults being used, not ours. """ - DefaultAioHttpClient = httpx.AsyncClient - """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" + DefaultAioHttpClient = httpx2.AsyncClient + """An alias to `httpx2.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" else: DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient DefaultAioHttpClient = _DefaultAioHttpClient @@ -1523,8 +1527,8 @@ def __del__(self) -> None: pass -class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]): - _client: httpx.AsyncClient +class AsyncAPIClient(BaseClient[httpx2.AsyncClient, AsyncStream[Any]]): + _client: httpx2.AsyncClient _default_stream_cls: type[AsyncStream[Any]] | None = None def __init__( @@ -1535,7 +1539,7 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = not_given, - http_client: httpx.AsyncClient | None = None, + http_client: httpx2.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: @@ -1556,7 +1560,7 @@ def __init__( if ( http_client is not None and not is_httpx2_async_client(http_client) - and not isinstance(http_client, httpx.AsyncClient) # pyright: ignore[reportUnnecessaryIsInstance] + and not is_legacy_httpx_async_client(http_client) ): raise TypeError( "Invalid `http_client` argument; Expected an instance of `httpx.AsyncClient` or " @@ -1609,7 +1613,7 @@ async def _prepare_options( async def _prepare_request( self, - request: httpx.Request, # noqa: ARG002 + request: httpx2.Request, # noqa: ARG002 ) -> None: """This method is used as a callback for mutating the `Request` object after it has been constructed. @@ -1620,11 +1624,11 @@ async def _prepare_request( async def _send_request( self, - request: httpx.Request, + request: httpx2.Request, *, stream: bool, **kwargs: Unpack[HttpxSendArgs], - ) -> httpx.Response: + ) -> httpx2.Response: return await self._client.send(request, stream=stream, **kwargs) @overload @@ -1679,7 +1683,7 @@ async def request( # ensure the idempotency key is reused between requests input_options.idempotency_key = self._idempotency_key() - response: httpx.Response | None = None + response: httpx2.Response | None = None max_retries = input_options.get_max_retries(self.max_retries) retries_taken = 0 @@ -1696,7 +1700,7 @@ async def request( kwargs["auth"] = ( normalize_httpx2_auth(self.custom_auth) if is_httpx2_async_client(self._client) - else self.custom_auth + else normalize_legacy_httpx_auth(self.custom_auth) ) if options.follow_redirects is not None: @@ -1789,7 +1793,7 @@ async def request( ) async def _sleep_for_retry( - self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx2.Response | None ) -> None: remaining_retries = max_retries - retries_taken if remaining_retries == 1: @@ -1807,7 +1811,7 @@ async def _process_response( *, cast_to: Type[ResponseT], options: FinalRequestOptions, - response: httpx.Response, + response: httpx2.Response, stream: bool, stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, retries_taken: int = 0, @@ -1853,7 +1857,7 @@ async def _process_response( ), ) - if cast_to == httpx.Response: + if cast_to in http_response_types(): return cast(ResponseT, response) api_response = AsyncAPIResponse( @@ -2090,7 +2094,7 @@ def make_request_options( extra_query: Query | None = None, extra_body: Body | None = None, idempotency_key: str | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, post_parser: PostParser | NotGiven = not_given, security: SecurityOptions | None = None, synthesize_event_and_data: bool | None = None, diff --git a/src/openai/_client.py b/src/openai/_client.py index a12a871f18..5f980c8cb6 100644 --- a/src/openai/_client.py +++ b/src/openai/_client.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any, Mapping, Callable, Awaitable from typing_extensions import Self, Unpack, override -import httpx +import httpx2 from . import _exceptions from ._qs import Querystring @@ -117,7 +117,7 @@ class OpenAI(SyncAPIClient): _provider: _Provider | None _provider_runtime: _ProviderRuntime | None - websocket_base_url: str | httpx.URL | None + websocket_base_url: str | httpx2.URL | None """Base URL for WebSocket connections. If not specified, the default base URL will be used, with 'wss://' replacing the @@ -135,16 +135,16 @@ def __init__( project: str | None = None, webhook_secret: str | None = None, provider: _Provider | None = None, - base_url: str | httpx.URL | None = None, - websocket_base_url: str | httpx.URL | None = None, + base_url: str | httpx2.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - # Configure a custom httpx client. + # Configure a custom httpx2 client. # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`. - # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details. - http_client: httpx.Client | None = None, + # See the [httpx2 documentation](https://httpx2.pydantic.dev/api/#client) for more details. + http_client: httpx2.Client | None = None, # Enable or disable schema validation for data returned by the API. # When enabled an error APIResponseValidationError is raised # if the API responds with invalid data for the expected schema. @@ -452,12 +452,12 @@ def qs(self) -> Querystring: def _send_with_auth_retry( self, - request: httpx.Request, + request: httpx2.Request, *, stream: bool, retried: bool = False, **kwargs: Unpack[HttpxSendArgs], - ) -> httpx.Response: + ) -> httpx2.Response: used_workload_identity_auth = False if self._workload_identity_auth is not None: @@ -483,11 +483,11 @@ def _send_with_auth_retry( @override def _send_request( self, - request: httpx.Request, + request: httpx2.Request, *, stream: bool, **kwargs: Unpack[HttpxSendArgs], - ) -> httpx.Response: + ) -> httpx2.Response: response = self._send_with_auth_retry(request, stream=stream, **kwargs) if self._provider_runtime is not None and self._provider_runtime.normalize_response is not None: response = self._provider_runtime.normalize_response(response) @@ -566,14 +566,14 @@ def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions: return super()._prepare_options(options) @override - def _prepare_request(self, request: httpx.Request) -> None: + def _prepare_request(self, request: httpx2.Request) -> None: if self._provider_runtime is not None and self._provider_runtime.prepare_request is not None: self._provider_runtime.prepare_request(request) @override - def _custom_auth(self, security: SecurityOptions) -> httpx.Auth | None: + def _custom_auth(self, security: SecurityOptions) -> httpx2.Auth | None: if self._provider_runtime is not None: - return httpx.Auth() + return httpx2.Auth() return super()._custom_auth(security) @@ -593,10 +593,10 @@ def copy( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, - base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, + base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, - http_client: httpx.Client | None = None, + http_client: httpx2.Client | None = None, max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, @@ -682,7 +682,7 @@ def _make_status_error( err_msg: str, *, body: object, - response: httpx.Response, + response: httpx2.Response, ) -> APIStatusError: data = body.get("error", body) if is_mapping(body) else body if response.status_code == 400: @@ -723,7 +723,7 @@ class AsyncOpenAI(AsyncAPIClient): _provider: _Provider | None _provider_runtime: _ProviderRuntime | None - websocket_base_url: str | httpx.URL | None + websocket_base_url: str | httpx2.URL | None """Base URL for WebSocket connections. If not specified, the default base URL will be used, with 'wss://' replacing the @@ -741,16 +741,16 @@ def __init__( project: str | None = None, webhook_secret: str | None = None, provider: _Provider | None = None, - base_url: str | httpx.URL | None = None, - websocket_base_url: str | httpx.URL | None = None, + base_url: str | httpx2.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - # Configure a custom httpx client. + # Configure a custom httpx2 client. # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`. - # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details. - http_client: httpx.AsyncClient | None = None, + # See the [httpx2 documentation](https://httpx2.pydantic.dev/api/#asyncclient) for more details. + http_client: httpx2.AsyncClient | None = None, # Enable or disable schema validation for data returned by the API. # When enabled an error APIResponseValidationError is raised # if the API responds with invalid data for the expected schema. @@ -1058,12 +1058,12 @@ def qs(self) -> Querystring: async def _send_with_auth_retry( self, - request: httpx.Request, + request: httpx2.Request, *, stream: bool, retried: bool = False, **kwargs: Unpack[HttpxSendArgs], - ) -> httpx.Response: + ) -> httpx2.Response: used_workload_identity_auth = False if self._workload_identity_auth is not None: @@ -1089,11 +1089,11 @@ async def _send_with_auth_retry( @override async def _send_request( self, - request: httpx.Request, + request: httpx2.Request, *, stream: bool, **kwargs: Unpack[HttpxSendArgs], - ) -> httpx.Response: + ) -> httpx2.Response: response = await self._send_with_auth_retry(request, stream=stream, **kwargs) if self._provider_runtime is not None: if self._provider_runtime.normalize_async_response is not None: @@ -1177,7 +1177,7 @@ async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOp return await super()._prepare_options(options) @override - async def _prepare_request(self, request: httpx.Request) -> None: + async def _prepare_request(self, request: httpx2.Request) -> None: if self._provider_runtime is None: return @@ -1188,9 +1188,9 @@ async def _prepare_request(self, request: httpx.Request) -> None: @property @override - def custom_auth(self) -> httpx.Auth | None: + def custom_auth(self) -> httpx2.Auth | None: if self._provider_runtime is not None: - return httpx.Auth() + return httpx2.Auth() return super().custom_auth @@ -1210,10 +1210,10 @@ def copy( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, - base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, + base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, - http_client: httpx.AsyncClient | None = None, + http_client: httpx2.AsyncClient | None = None, max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, @@ -1298,7 +1298,7 @@ def _make_status_error( err_msg: str, *, body: object, - response: httpx.Response, + response: httpx2.Response, ) -> APIStatusError: data = body.get("error", body) if is_mapping(body) else body if response.status_code == 400: diff --git a/src/openai/_constants.py b/src/openai/_constants.py index fd73c6485d..fa137439f1 100644 --- a/src/openai/_constants.py +++ b/src/openai/_constants.py @@ -1,14 +1,14 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import httpx +import httpx2 RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response" OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to" # default timeout is 10 minutes -DEFAULT_TIMEOUT = httpx.Timeout(timeout=600, connect=5.0) +DEFAULT_TIMEOUT = httpx2.Timeout(timeout=600, connect=5.0) DEFAULT_MAX_RETRIES = 2 -DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=1000, max_keepalive_connections=100) +DEFAULT_CONNECTION_LIMITS = httpx2.Limits(max_connections=1000, max_keepalive_connections=100) INITIAL_RETRY_DELAY = 0.5 MAX_RETRY_DELAY = 8.0 diff --git a/src/openai/_exceptions.py b/src/openai/_exceptions.py index 86f44b0e15..b7be6a07e0 100644 --- a/src/openai/_exceptions.py +++ b/src/openai/_exceptions.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Optional, cast from typing_extensions import Literal -import httpx +import httpx2 from ._utils import is_dict from ._models import construct_type @@ -38,16 +38,16 @@ class OpenAIError(Exception): class SubjectTokenProviderError(OpenAIError): - response: httpx.Response | None + response: httpx2.Response | None - def __init__(self, message: str, *, response: httpx.Response | None = None) -> None: + def __init__(self, message: str, *, response: httpx2.Response | None = None) -> None: super().__init__(message) self.response = response class APIError(OpenAIError): message: str - request: httpx.Request + request: httpx2.Request body: object | None """The API response body. @@ -64,7 +64,7 @@ class APIError(OpenAIError): param: Optional[str] = None type: Optional[str] - def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None: + def __init__(self, message: str, request: httpx2.Request, *, body: object | None) -> None: super().__init__(message) self.request = request self.message = message @@ -81,10 +81,10 @@ def __init__(self, message: str, request: httpx.Request, *, body: object | None) class APIResponseValidationError(APIError): - response: httpx.Response + response: httpx2.Response status_code: int - def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None: + def __init__(self, response: httpx2.Response, body: object | None, *, message: str | None = None) -> None: super().__init__(message or "Data returned by API invalid for expected schema.", response.request, body=body) self.response = response self.status_code = response.status_code @@ -93,11 +93,11 @@ def __init__(self, response: httpx.Response, body: object | None, *, message: st class APIStatusError(APIError): """Raised when an API response has a status code of 4xx or 5xx.""" - response: httpx.Response + response: httpx2.Response status_code: int request_id: str | None - def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None: + def __init__(self, message: str, *, response: httpx2.Response, body: object | None) -> None: super().__init__(message, response.request, body=body) self.response = response self.status_code = response.status_code @@ -105,12 +105,12 @@ def __init__(self, message: str, *, response: httpx.Response, body: object | Non class APIConnectionError(APIError): - def __init__(self, *, message: str = "Connection error.", request: httpx.Request) -> None: + def __init__(self, *, message: str = "Connection error.", request: httpx2.Request) -> None: super().__init__(message, request, body=None) class APITimeoutError(APIConnectionError): - def __init__(self, request: httpx.Request) -> None: + def __init__(self, request: httpx2.Request) -> None: super().__init__(message="Request timed out.", request=request) @@ -125,7 +125,7 @@ class AuthenticationError(APIStatusError): class OAuthError(AuthenticationError): error: Optional[OAuthErrorCode] - def __init__(self, *, response: httpx.Response, body: object | None) -> None: + def __init__(self, *, response: httpx2.Response, body: object | None) -> None: message = "OAuth authentication error." error = None diff --git a/src/openai/_httpx2.py b/src/openai/_httpx2.py index 7dd3d8d6d9..491398b43c 100644 --- a/src/openai/_httpx2.py +++ b/src/openai/_httpx2.py @@ -1,151 +1,141 @@ from __future__ import annotations import sys -import importlib from typing import Any, Protocol, cast -import httpx +import httpx2 from ._constants import DEFAULT_TIMEOUT, DEFAULT_CONNECTION_LIMITS -class _Httpx2Module(Protocol): - Auth: type[httpx.Auth] - Client: type[httpx.Client] - AsyncClient: type[httpx.AsyncClient] - URL: type[httpx.URL] - Response: type[httpx.Response] - Timeout: type[httpx.Timeout] - Limits: type[httpx.Limits] - TimeoutException: type[httpx.TimeoutException] - HTTPStatusError: type[httpx.HTTPStatusError] - StreamConsumed: type[httpx.StreamConsumed] - RequestNotRead: type[httpx.RequestNotRead] +class _LegacyHttpxModule(Protocol): + Auth: type[httpx2.Auth] + Client: type[httpx2.Client] + AsyncClient: type[httpx2.AsyncClient] + URL: type[httpx2.URL] + Response: type[httpx2.Response] + Timeout: type[httpx2.Timeout] + Limits: type[httpx2.Limits] + TimeoutException: type[httpx2.TimeoutException] + HTTPStatusError: type[httpx2.HTTPStatusError] + StreamConsumed: type[httpx2.StreamConsumed] + RequestNotRead: type[httpx2.RequestNotRead] -def _loaded_httpx2() -> _Httpx2Module | None: - module = sys.modules.get("httpx2") - if module is None: - return None - return cast(_Httpx2Module, module) +def _loaded_legacy_httpx() -> _LegacyHttpxModule | None: + module = sys.modules.get("httpx") + return cast(_LegacyHttpxModule, module) if module is not None else None -def _supports_httpx2() -> bool: - return sys.version_info >= (3, 10) - - -def _require_httpx2() -> _Httpx2Module: - if not _supports_httpx2(): - raise RuntimeError( - "HTTPX2 requires Python 3.10 or later; install the httpx2 extra on a supported interpreter: " - "pip install 'openai[httpx2]'" - ) +def is_httpx2_sync_client(value: object) -> bool: + return isinstance(value, httpx2.Client) - try: - module = importlib.import_module("httpx2") - except ImportError: - raise RuntimeError("To use HTTPX2, install the httpx2 extra: pip install 'openai[httpx2]'") from None - return cast(_Httpx2Module, module) +def is_httpx2_async_client(value: object) -> bool: + return isinstance(value, httpx2.AsyncClient) -def is_httpx2_sync_client(value: object) -> bool: - module = _loaded_httpx2() +def is_legacy_httpx_sync_client(value: object) -> bool: + module = _loaded_legacy_httpx() return module is not None and isinstance(value, module.Client) -def is_httpx2_async_client(value: object) -> bool: - module = _loaded_httpx2() +def is_legacy_httpx_async_client(value: object) -> bool: + module = _loaded_legacy_httpx() return module is not None and isinstance(value, module.AsyncClient) -def normalize_httpx_url(value: str | httpx.URL) -> httpx.URL: - module = _loaded_httpx2() - if module is not None and isinstance(value, module.URL): - return httpx.URL(str(value)) - if isinstance(value, httpx.URL): +def normalize_httpx_url(value: str | httpx2.URL) -> httpx2.URL: + if isinstance(value, httpx2.URL): return value - return httpx.URL(value) + module = _loaded_legacy_httpx() + legacy_value: object = value + if module is not None and isinstance(legacy_value, module.URL): + return httpx2.URL(str(legacy_value)) + + return httpx2.URL(value) -def http_response_types() -> tuple[type[httpx.Response], ...]: - module = _loaded_httpx2() - if module is None: - return (httpx.Response,) - return (httpx.Response, module.Response) +def http_response_types() -> tuple[type[httpx2.Response], ...]: + module = _loaded_legacy_httpx() + return (httpx2.Response,) if module is None else (httpx2.Response, module.Response) -def normalize_httpx_timeout(value: float | httpx.Timeout | None) -> float | httpx.Timeout | None: - module = _loaded_httpx2() + +def normalize_httpx_timeout(value: float | httpx2.Timeout | None) -> float | httpx2.Timeout | None: + module = _loaded_legacy_httpx() if module is not None and isinstance(value, module.Timeout): - return httpx.Timeout(**value.as_dict()) + return httpx2.Timeout(**value.as_dict()) return value -def normalize_httpx2_timeout(value: float | httpx.Timeout | None) -> float | httpx.Timeout | None: - if isinstance(value, httpx.Timeout): - return _require_httpx2().Timeout(**value.as_dict()) +def normalize_httpx2_timeout(value: float | httpx2.Timeout | None) -> float | httpx2.Timeout | None: + return normalize_httpx_timeout(value) + + +def normalize_legacy_httpx_timeout(value: float | httpx2.Timeout | None) -> float | httpx2.Timeout | None: + module = _loaded_legacy_httpx() + if module is not None and isinstance(value, httpx2.Timeout): + return module.Timeout(**value.as_dict()) return value -def normalize_httpx2_auth(value: httpx.Auth) -> httpx.Auth: - if type(value) is httpx.Auth: - return _require_httpx2().Auth() +def normalize_httpx2_auth(value: httpx2.Auth) -> httpx2.Auth: + module = _loaded_legacy_httpx() + if module is not None and type(value) is module.Auth: + return httpx2.Auth() return value -def timeout_exceptions() -> tuple[type[httpx.TimeoutException], ...]: - module = _loaded_httpx2() - if module is None: - return (httpx.TimeoutException,) - return (httpx.TimeoutException, module.TimeoutException) +def normalize_legacy_httpx_auth(value: httpx2.Auth) -> httpx2.Auth: + module = _loaded_legacy_httpx() + if module is not None and type(value) is httpx2.Auth: + return module.Auth() + return value -def status_exceptions() -> tuple[type[httpx.HTTPStatusError], ...]: - module = _loaded_httpx2() - if module is None: - return (httpx.HTTPStatusError,) - return (httpx.HTTPStatusError, module.HTTPStatusError) +def timeout_exceptions() -> tuple[type[httpx2.TimeoutException], ...]: + module = _loaded_legacy_httpx() + return (httpx2.TimeoutException,) if module is None else (httpx2.TimeoutException, module.TimeoutException) -def stream_consumed_exceptions() -> tuple[type[httpx.StreamConsumed], ...]: - module = _loaded_httpx2() - if module is None: - return (httpx.StreamConsumed,) - return (httpx.StreamConsumed, module.StreamConsumed) +def status_exceptions() -> tuple[type[httpx2.HTTPStatusError], ...]: + module = _loaded_legacy_httpx() + return (httpx2.HTTPStatusError,) if module is None else (httpx2.HTTPStatusError, module.HTTPStatusError) -def request_not_read_exceptions() -> tuple[type[httpx.RequestNotRead], ...]: - module = _loaded_httpx2() - if module is None: - return (httpx.RequestNotRead,) - return (httpx.RequestNotRead, module.RequestNotRead) +def stream_consumed_exceptions() -> tuple[type[httpx2.StreamConsumed], ...]: + module = _loaded_legacy_httpx() + return (httpx2.StreamConsumed,) if module is None else (httpx2.StreamConsumed, module.StreamConsumed) -def _set_httpx2_defaults(kwargs: dict[str, Any]) -> _Httpx2Module: - module = _require_httpx2() - timeout = kwargs.get("timeout", DEFAULT_TIMEOUT) - kwargs["timeout"] = normalize_httpx2_timeout(timeout) +def request_not_read_exceptions() -> tuple[type[httpx2.RequestNotRead], ...]: + module = _loaded_legacy_httpx() + return (httpx2.RequestNotRead,) if module is None else (httpx2.RequestNotRead, module.RequestNotRead) + + +def _set_httpx2_defaults(kwargs: dict[str, Any]) -> None: + kwargs["timeout"] = normalize_httpx2_timeout(kwargs.get("timeout", DEFAULT_TIMEOUT)) limits = kwargs.get("limits", DEFAULT_CONNECTION_LIMITS) - if isinstance(limits, httpx.Limits): - kwargs["limits"] = module.Limits( + module = _loaded_legacy_httpx() + if module is not None and isinstance(limits, module.Limits): + limits = httpx2.Limits( max_connections=limits.max_connections, max_keepalive_connections=limits.max_keepalive_connections, keepalive_expiry=limits.keepalive_expiry, ) - + kwargs["limits"] = limits kwargs.setdefault("follow_redirects", True) - return module -def DefaultHttpx2Client(**kwargs: Any) -> httpx.Client: - """Create an experimental HTTPX2 client with the SDK's recommended defaults.""" - module = _set_httpx2_defaults(kwargs) - return module.Client(**kwargs) +def DefaultHttpx2Client(**kwargs: Any) -> httpx2.Client: + """Create an HTTPX2 client with the SDK's recommended defaults.""" + _set_httpx2_defaults(kwargs) + return httpx2.Client(**kwargs) -def DefaultAsyncHttpx2Client(**kwargs: Any) -> httpx.AsyncClient: - """Create an experimental async HTTPX2 client with the SDK's recommended defaults.""" - module = _set_httpx2_defaults(kwargs) - return module.AsyncClient(**kwargs) +def DefaultAsyncHttpx2Client(**kwargs: Any) -> httpx2.AsyncClient: + """Create an async HTTPX2 client with the SDK's recommended defaults.""" + _set_httpx2_defaults(kwargs) + return httpx2.AsyncClient(**kwargs) diff --git a/src/openai/_legacy_response.py b/src/openai/_legacy_response.py index 542bd6c660..04a927c430 100644 --- a/src/openai/_legacy_response.py +++ b/src/openai/_legacy_response.py @@ -20,7 +20,7 @@ from typing_extensions import Awaitable, ParamSpec, override, deprecated, get_origin import anyio -import httpx +import httpx2 import pydantic from ._types import NoneType @@ -63,7 +63,7 @@ class LegacyAPIResponse(Generic[R]): _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None _options: FinalRequestOptions - http_response: httpx.Response + http_response: httpx2.Response retries_taken: int """The number of retries made. If no retries happened this will be `0`""" @@ -71,7 +71,7 @@ class LegacyAPIResponse(Generic[R]): def __init__( self, *, - raw: httpx.Response, + raw: httpx2.Response, cast_to: type[R], client: BaseClient[Any, Any], stream: bool, @@ -128,7 +128,7 @@ class MyModel(BaseModel): - `str` - `int` - `float` - - `httpx.Response` + - `httpx2.Response` """ cache_key = to if to is not None else self._cast_to cached = self._parsed_by_type.get(cache_key) @@ -146,11 +146,11 @@ class MyModel(BaseModel): return cast(R, parsed) @property - def headers(self) -> httpx.Headers: + def headers(self) -> httpx2.Headers: return self.http_response.headers @property - def http_request(self) -> httpx.Request: + def http_request(self) -> httpx2.Request: return self.http_response.request @property @@ -158,7 +158,7 @@ def status_code(self) -> int: return self.http_response.status_code @property - def url(self) -> httpx.URL: + def url(self) -> httpx2.URL: return self.http_response.url @property @@ -303,7 +303,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: and not issubclass(origin, BaseModel) ): raise RuntimeError( - f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}." + f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx2.Response}." ) # split is required to handle cases where additional information is included @@ -389,9 +389,9 @@ async def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]: class HttpxBinaryResponseContent: - response: httpx.Response + response: httpx2.Response - def __init__(self, response: httpx.Response) -> None: + def __init__(self, response: httpx2.Response) -> None: self.response = response @property diff --git a/src/openai/_provider.py b/src/openai/_provider.py index 2874d11ff3..8a6c83e502 100644 --- a/src/openai/_provider.py +++ b/src/openai/_provider.py @@ -4,7 +4,7 @@ from weakref import WeakKeyDictionary from dataclasses import dataclass -import httpx +import httpx2 from ._models import FinalRequestOptions from ._exceptions import OpenAIError @@ -19,13 +19,13 @@ class _Provider: @dataclass class _ProviderRuntime: name: str - base_url: str | httpx.URL + base_url: str | httpx2.URL transform_request: Callable[[FinalRequestOptions], FinalRequestOptions] | None = None transform_async_request: Callable[[FinalRequestOptions], Awaitable[FinalRequestOptions]] | None = None - prepare_request: Callable[[httpx.Request], None] | None = None - prepare_async_request: Callable[[httpx.Request], Awaitable[None]] | None = None - normalize_response: Callable[[httpx.Response], httpx.Response] | None = None - normalize_async_response: Callable[[httpx.Response], Awaitable[httpx.Response]] | None = None + prepare_request: Callable[[httpx2.Request], None] | None = None + prepare_async_request: Callable[[httpx2.Request], Awaitable[None]] | None = None + normalize_response: Callable[[httpx2.Response], httpx2.Response] | None = None + normalize_async_response: Callable[[httpx2.Response], Awaitable[httpx2.Response]] | None = None class _ProviderDefinition(Protocol): diff --git a/src/openai/_response.py b/src/openai/_response.py index 5a790d69a2..373b7bc9f4 100644 --- a/src/openai/_response.py +++ b/src/openai/_response.py @@ -21,7 +21,7 @@ from typing_extensions import Awaitable, ParamSpec, override, get_origin import anyio -import httpx +import httpx2 import pydantic from ._types import NoneType @@ -54,7 +54,7 @@ class BaseAPIResponse(Generic[R]): _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None _options: FinalRequestOptions - http_response: httpx.Response + http_response: httpx2.Response retries_taken: int """The number of retries made. If no retries happened this will be `0`""" @@ -62,7 +62,7 @@ class BaseAPIResponse(Generic[R]): def __init__( self, *, - raw: httpx.Response, + raw: httpx2.Response, cast_to: type[R], client: BaseClient[Any, Any], stream: bool, @@ -80,12 +80,12 @@ def __init__( self.retries_taken = retries_taken @property - def headers(self) -> httpx.Headers: + def headers(self) -> httpx2.Headers: return self.http_response.headers @property - def http_request(self) -> httpx.Request: - """Returns the httpx Request instance associated with the current response.""" + def http_request(self) -> httpx2.Request: + """Returns the HTTPX2 Request instance associated with the current response.""" return self.http_response.request @property @@ -93,7 +93,7 @@ def status_code(self) -> int: return self.http_response.status_code @property - def url(self) -> httpx.URL: + def url(self) -> httpx2.URL: """Returns the URL for which the request was made.""" return self.http_response.url @@ -236,7 +236,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: and not issubclass(origin, BaseModel) ): raise RuntimeError( - f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}." + f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx2.Response}." ) # split is required to handle cases where additional information is included @@ -315,7 +315,7 @@ class MyModel(BaseModel): - `str` - `int` - `float` - - `httpx.Response` + - `httpx2.Response` """ cache_key = to if to is not None else self._cast_to cached = self._parsed_by_type.get(cache_key) @@ -422,7 +422,7 @@ class MyModel(BaseModel): - `list` - `Union` - `str` - - `httpx.Response` + - `httpx2.Response` """ cache_key = to if to is not None else self._cast_to cached = self._parsed_by_type.get(cache_key) diff --git a/src/openai/_streaming.py b/src/openai/_streaming.py index 45c13cc11d..61ed058bd1 100644 --- a/src/openai/_streaming.py +++ b/src/openai/_streaming.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable -import httpx +import httpx2 from ._utils import is_mapping, extract_type_var_from_base from ._exceptions import APIError @@ -23,7 +23,7 @@ class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" - response: httpx.Response + response: httpx2.Response _options: Optional[FinalRequestOptions] = None _decoder: SSEBytesDecoder @@ -31,7 +31,7 @@ def __init__( self, *, cast_to: type[_T], - response: httpx.Response, + response: httpx2.Response, client: OpenAI, options: Optional[FinalRequestOptions] = None, ) -> None: @@ -132,7 +132,7 @@ def close(self) -> None: class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" - response: httpx.Response + response: httpx2.Response _options: Optional[FinalRequestOptions] = None _decoder: SSEDecoder | SSEBytesDecoder @@ -140,7 +140,7 @@ def __init__( self, *, cast_to: type[_T], - response: httpx.Response, + response: httpx2.Response, client: AsyncOpenAI, options: Optional[FinalRequestOptions] = None, ) -> None: diff --git a/src/openai/_types.py b/src/openai/_types.py index 9936b00f73..d49aab29d5 100644 --- a/src/openai/_types.py +++ b/src/openai/_types.py @@ -31,9 +31,9 @@ runtime_checkable, ) -import httpx +import httpx2 import pydantic -from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport +from httpx2 import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport if TYPE_CHECKING: from ._models import BaseModel, SecurityOptions @@ -251,7 +251,7 @@ class _GenericAlias(Protocol): class HttpxSendArgs(TypedDict, total=False): - auth: httpx.Auth + auth: httpx2.Auth follow_redirects: bool diff --git a/src/openai/_utils/_logs.py b/src/openai/_utils/_logs.py index eaffa5ec7a..a997609314 100644 --- a/src/openai/_utils/_logs.py +++ b/src/openai/_utils/_logs.py @@ -5,7 +5,7 @@ from ._utils import is_dict logger: logging.Logger = logging.getLogger("openai") -httpx_logger: logging.Logger = logging.getLogger("httpx") +httpx2_logger: logging.Logger = logging.getLogger("httpx2") SENSITIVE_HEADERS = {"api-key", "authorization", "x-amz-security-token"} @@ -24,11 +24,11 @@ def setup_logging() -> None: if env == "debug": _basic_config() logger.setLevel(logging.DEBUG) - httpx_logger.setLevel(logging.DEBUG) + httpx2_logger.setLevel(logging.DEBUG) elif env == "info": _basic_config() logger.setLevel(logging.INFO) - httpx_logger.setLevel(logging.INFO) + httpx2_logger.setLevel(logging.INFO) class SensitiveHeadersFilter(logging.Filter): diff --git a/src/openai/_vendor/__init__.py b/src/openai/_vendor/__init__.py new file mode 100644 index 0000000000..16d93a5f02 --- /dev/null +++ b/src/openai/_vendor/__init__.py @@ -0,0 +1 @@ +"""Small attributed third-party components required by optional SDK integrations.""" diff --git a/src/openai/_vendor/httpx_aiohttp/FORK.md b/src/openai/_vendor/httpx_aiohttp/FORK.md new file mode 100644 index 0000000000..d66699bce0 --- /dev/null +++ b/src/openai/_vendor/httpx_aiohttp/FORK.md @@ -0,0 +1,13 @@ +# HTTPX2-native aiohttp adapter + +This directory vendors the HTTPX2-specific adapter from httpx-aiohttp 0.2.0. + +- Upstream: https://github.com/karpetrosyan/httpx-aiohttp +- Upstream tag: `0.2.0` +- Upstream commit: `52266a66f6bd73f828133d0fd09114179fd45b60` +- License: BSD 3-Clause; the original license is preserved in `LICENSE`. +- The upstream README is preserved without modification in `README.md`. + +The upstream distribution always installs and imports legacy HTTPX even when only its HTTPX2 adapter is used. Vendoring the three HTTPX2-specific modules lets `openai[aiohttp]` depend only on HTTPX2 and aiohttp while preserving the upstream transport behavior. No functional changes have been made to the upstream adapter. + +The fork may be removed when upstream provides an HTTPX2-only distribution or makes legacy HTTPX genuinely optional. diff --git a/src/openai/_vendor/httpx_aiohttp/LICENSE b/src/openai/_vendor/httpx_aiohttp/LICENSE new file mode 100644 index 0000000000..f12539fab7 --- /dev/null +++ b/src/openai/_vendor/httpx_aiohttp/LICENSE @@ -0,0 +1,27 @@ +Copyright © 2025, Karen Petrosyan. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/openai/_vendor/httpx_aiohttp/README.md b/src/openai/_vendor/httpx_aiohttp/README.md new file mode 100644 index 0000000000..938283c9f9 --- /dev/null +++ b/src/openai/_vendor/httpx_aiohttp/README.md @@ -0,0 +1,25 @@ + +

httpx-aiohttp - provides transports for httpx to work on top of aiohttp, handling all high-level features like authentication, retries, and cookies through httpx, while delegating low-level socket-level HTTP messaging to aiohttp

+ +

+ + + pypi + +

+ +## Installation + +```shell +uv pip install httpx-aiohttp +``` + +For [httpx2](https://github.com/pydantic/httpx2) support, install the `httpx2` extra and use `httpx_aiohttp.httpx2.Httpx2AiohttpClient` / `httpx_aiohttp.httpx2.AiohttpTransport`: + +```shell +uv pip install httpx-aiohttp[httpx2] +``` + +## Documentation + +Project documentation is available at https://karpetrosyan.github.io/httpx-aiohttp/ diff --git a/src/openai/_vendor/httpx_aiohttp/__init__.py b/src/openai/_vendor/httpx_aiohttp/__init__.py new file mode 100644 index 0000000000..3029b3b72c --- /dev/null +++ b/src/openai/_vendor/httpx_aiohttp/__init__.py @@ -0,0 +1,9 @@ +"""Variant of httpx_aiohttp for `httpx2 `_. + +Requires the optional ``httpx2`` dependency: ``pip install httpx-aiohttp[httpx2]``. +""" + +from .client import Httpx2AiohttpClient +from .transport import AiohttpTransport + +__all__ = ["AiohttpTransport", "Httpx2AiohttpClient"] diff --git a/src/openai/_vendor/httpx_aiohttp/client.py b/src/openai/_vendor/httpx_aiohttp/client.py new file mode 100644 index 0000000000..1187fcb1da --- /dev/null +++ b/src/openai/_vendor/httpx_aiohttp/client.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import ssl +import typing as t + +import httpx2 as httpx + +SOCKET_OPTION = t.Union[ + t.Tuple[int, int, int], + t.Tuple[int, int, t.Union[bytes, bytearray]], + t.Tuple[int, int, None, int], +] + + +class Httpx2AiohttpClient(httpx.AsyncClient): + def _init_transport( + self, + verify: ssl.SSLContext | str | bool = True, + cert: t.Union[str, t.Tuple[str, str], t.Tuple[str, str, str], None] = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: httpx.Limits = httpx.Limits(max_connections=100, max_keepalive_connections=20), + transport: httpx.AsyncBaseTransport | None = None, + **kwargs: t.Any, + ) -> httpx.AsyncBaseTransport: + from .transport import AiohttpTransport + + if transport is not None: + return transport + + return AiohttpTransport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + ) + + def _init_proxy_transport( + self, + proxy: httpx.Proxy, + verify: ssl.SSLContext | str | bool = True, + cert: t.Union[str, t.Tuple[str, str], t.Tuple[str, str, str], None] = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: httpx.Limits = httpx.Limits(max_connections=100, max_keepalive_connections=20), + **kwargs: t.Any, + ) -> httpx.AsyncBaseTransport: + from .transport import AiohttpTransport + + return AiohttpTransport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + proxy=proxy, + ) diff --git a/src/openai/_vendor/httpx_aiohttp/transport.py b/src/openai/_vendor/httpx_aiohttp/transport.py new file mode 100644 index 0000000000..263bfe106d --- /dev/null +++ b/src/openai/_vendor/httpx_aiohttp/transport.py @@ -0,0 +1,201 @@ +from __future__ import annotations + +import contextlib +import ssl +import typing +import typing as t +from importlib import metadata +from logging import warning + +import aiohttp +import httpx2 as httpx +from aiohttp import BasicAuth, ClientTimeout +from aiohttp.client import ClientResponse, ClientSession + +AIOHTTP_EXC_MAP = { + aiohttp.ServerTimeoutError: httpx.TimeoutException, + aiohttp.SocketTimeoutError: httpx.ReadTimeout, + aiohttp.ClientConnectionError: httpx.ConnectTimeout, + aiohttp.ClientConnectorError: httpx.ConnectError, + aiohttp.ClientPayloadError: httpx.ReadError, + aiohttp.ClientProxyConnectionError: httpx.ProxyError, + aiohttp.ClientHttpProxyError: httpx.ProxyError, +} + +if metadata.version("aiohttp") >= "3.10.0": + AIOHTTP_EXC_MAP.update( + { + aiohttp.client_exceptions.NonHttpUrlClientError: httpx.UnsupportedProtocol, # type: ignore[reportAttributeAccessIssue] + aiohttp.client_exceptions.InvalidUrlClientError: httpx.UnsupportedProtocol, # type: ignore[reportAttributeAccessIssue] + } + ) + +SOCKET_OPTION = t.Union[ + t.Tuple[int, int, int], + t.Tuple[int, int, t.Union[bytes, bytearray]], + t.Tuple[int, int, None, int], +] + + +@contextlib.contextmanager +def map_aiohttp_exceptions() -> typing.Iterator[None]: + try: + yield + except Exception as exc: + mapped_exc = None + + for from_exc, to_exc in AIOHTTP_EXC_MAP.items(): + if not isinstance(exc, from_exc): # type: ignore + continue + if mapped_exc is None or issubclass(to_exc, mapped_exc): + mapped_exc = to_exc + + if mapped_exc is None: # pragma: no cover + raise + + message = str(exc) + raise mapped_exc(message) from exc + + +class AiohttpResponseStream(httpx.AsyncByteStream): + CHUNK_SIZE = 1024 * 16 + + def __init__(self, aiohttp_response: ClientResponse) -> None: + self._aiohttp_response = aiohttp_response + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + with map_aiohttp_exceptions(): + async for chunk in self._aiohttp_response.content.iter_chunked(self.CHUNK_SIZE): + yield chunk + + async def aclose(self) -> None: + with map_aiohttp_exceptions(): + await self._aiohttp_response.__aexit__(None, None, None) + + +class AiohttpTransport(httpx.AsyncBaseTransport): + def __init__( + self, + verify: ssl.SSLContext | str | bool = True, + cert: t.Union[str, t.Tuple[str, str], t.Tuple[str, str, str], None] = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: httpx.Limits = httpx.Limits(max_connections=100, max_keepalive_connections=20), + proxy: httpx.Proxy | None = None, + uds: str | None = None, + local_address: str | None = None, + retries: int = 0, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + client: ClientSession | t.Callable[[], ClientSession] | None = None, + # Additional keyword arguments for future compatibility + # If httpx decides to add one, we won't break the API + **kwargs: t.Dict[str, t.Any], + ) -> None: + if http2: + if not http1: + raise httpx.UnsupportedProtocol("HTTP/2 is not supported by aiohttp transport, use HTTP/1.1 instead.") + warning("HTTP/2 is not supported by aiohttp transport, using HTTP/1.1 instead.") + + ssl_context = httpx.create_ssl_context( + verify=verify, + cert=cert, + trust_env=trust_env, + ) + + self.ssl_context = ssl_context + self.proxy = proxy + self.limits = limits + self.retries = retries + self.socket_options = socket_options or [] + self.uds = uds + self.local_address = local_address + + self.client = client + + def get_client(self) -> ClientSession: + if callable(self.client): + return self.client() + elif isinstance(self.client, ClientSession): + return self.client + else: + limit_kwarg = ( + { + "limit": self.limits.max_connections, + } + if self.limits.max_connections is not None + else {} + ) + if self.uds: + connector = aiohttp.UnixConnector( + path=self.uds, + keepalive_timeout=self.limits.keepalive_expiry, + **limit_kwarg, # type: ignore + ) + else: + connector = aiohttp.TCPConnector( + keepalive_timeout=self.limits.keepalive_expiry, + ssl=self.ssl_context, + local_addr=(self.local_address, 0) if self.local_address else None, + **limit_kwarg, # type: ignore + ) + return ClientSession(connector=connector) + + async def handle_async_request( + self, + request: httpx.Request, + ) -> httpx.Response: + if not isinstance(self.client, ClientSession): + self.client = self.get_client() + + timeout = request.extensions.get("timeout", {}) + sni_hostname = request.extensions.get("sni_hostname") + + with map_aiohttp_exceptions(): + data: t.Union[bytes, httpx.AsyncByteStream, None] + try: + data = request.content + if data == b"": + data = None + + except httpx.RequestNotRead: + data = request.stream # type: ignore + request.headers.pop("transfer-encoding", None) # handled by aiohttp + + response = await self.client.request( + method=request.method, + url=str(request.url) if request.url else "https://127.0.0.1:8000/", + headers=request.headers, + data=data, + allow_redirects=False, + auto_decompress=False, + compress=False, + timeout=ClientTimeout( + sock_connect=timeout.get("connect"), + sock_read=timeout.get("read"), + connect=timeout.get("pool"), + ), + server_hostname=sni_hostname, + proxy=str(self.proxy.url) if self.proxy else None, + proxy_auth=BasicAuth(self.proxy.auth[0], self.proxy.auth[1]) + if self.proxy and self.proxy.auth + else None, + proxy_headers=self.proxy.headers if self.proxy else None, + ).__aenter__() + + extensions = {"http_version": b"HTTP/1.1"} + + if response.reason: + extensions["reason_phrase"] = response.reason.encode() + + return httpx.Response( + status_code=response.status, + headers=response.raw_headers, + stream=AiohttpResponseStream(response), + request=request, + extensions=extensions, + ) + + async def aclose(self) -> None: + if isinstance(self.client, ClientSession): + await self.client.close() diff --git a/src/openai/_version.py b/src/openai/_version.py index 4f283c8b56..8889c08af5 100644 --- a/src/openai/_version.py +++ b/src/openai/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "openai" -__version__ = "2.54.0" # x-release-please-version +__version__ = "3.0.0" # x-release-please-version diff --git a/src/openai/auth/_workload.py b/src/openai/auth/_workload.py index 1c1efe536d..73b110b445 100644 --- a/src/openai/auth/_workload.py +++ b/src/openai/auth/_workload.py @@ -6,9 +6,9 @@ from pathlib import Path from typing_extensions import Literal, NotRequired -import httpx +import httpx2 -from .._httpx2 import DefaultHttpx2Client +from .._httpx2 import DefaultHttpx2Client, _loaded_legacy_httpx from .._exceptions import OAuthError, OpenAIError, SubjectTokenProviderError from .._utils._sync import to_thread @@ -75,7 +75,7 @@ def azure_managed_identity_token_provider( msi_res_id: str | None = None, api_version: str = "2018-02-01", timeout: float = 10.0, - http_client: httpx.Client | None = None, + http_client: httpx2.Client | None = None, ) -> SubjectTokenProvider: """ Get a subject token provider for Azure Managed Identities. @@ -89,7 +89,7 @@ def azure_managed_identity_token_provider( msi_res_id: the ARM resource ID of the managed identity to use, when multiple are assigned. api_version: the Azure IMDS API version. Defaults to `2018-02-01`. timeout: the request timeout in seconds. Defaults to 10.0. - http_client: optional httpx.Client instance to use for requests. If not provided, a new client will be created for each request. + http_client: optional httpx2.Client instance to use for requests. If not provided, a new client will be created for each request. """ def get_token() -> str: @@ -106,7 +106,7 @@ def get_token() -> str: if http_client is not None: response = http_client.get(url, params=params, headers={"Metadata": "true"}, timeout=timeout) else: - with httpx.Client() as client: + with httpx2.Client() as client: response = client.get(url, params=params, headers={"Metadata": "true"}, timeout=timeout) if response.is_error: @@ -131,7 +131,7 @@ def gcp_id_token_provider( audience: str = "https://api.openai.com/v1", *, timeout: float = 10.0, - http_client: httpx.Client | None = None, + http_client: httpx2.Client | None = None, ) -> SubjectTokenProvider: """ Get a subject token provider for GCP VM instances using the instance metadata server. @@ -142,7 +142,7 @@ def gcp_id_token_provider( audience: the unique URI agreed upon by both the instance and the system verifying the instance's identity. Defaults to `https://api.openai.com/v1`. timeout: the request timeout in seconds. Defaults to 10.0. - http_client: optional httpx.Client instance to use for requests. If not provided, a new client will be created for each request. + http_client: optional httpx2.Client instance to use for requests. If not provided, a new client will be created for each request. """ def get_token() -> str: @@ -153,7 +153,7 @@ def get_token() -> str: if http_client is not None: response = http_client.get(url, params=params, headers={"Metadata-Flavor": "Google"}, timeout=timeout) else: - with httpx.Client() as client: + with httpx2.Client() as client: response = client.get(url, params=params, headers={"Metadata-Flavor": "Google"}, timeout=timeout) if response.is_error: @@ -177,7 +177,7 @@ def __init__( *, workload_identity: WorkloadIdentity, token_exchange_url: str = DEFAULT_TOKEN_EXCHANGE_URL, - _use_httpx2: bool = False, + _use_httpx2: bool = True, ): self.workload_identity = workload_identity self.token_exchange_url = token_exchange_url @@ -248,7 +248,10 @@ def _fetch_token_from_exchange(self) -> dict[str, Any]: f"Unsupported token type: {token_type!r}. Supported types: {', '.join(SUBJECT_TOKEN_TYPES.keys())}" ) - exchange_client = DefaultHttpx2Client(follow_redirects=False) if self._use_httpx2 else httpx.Client() + legacy_httpx = _loaded_legacy_httpx() if not self._use_httpx2 else None + exchange_client = ( + legacy_httpx.Client() if legacy_httpx is not None else DefaultHttpx2Client(follow_redirects=False) + ) with exchange_client as client: response = client.post( self.token_exchange_url, @@ -263,7 +266,7 @@ def _fetch_token_from_exchange(self) -> dict[str, Any]: ) return self._handle_token_response(response) - def _handle_token_response(self, response: httpx.Response) -> dict[str, Any]: + def _handle_token_response(self, response: httpx2.Response) -> dict[str, Any]: try: body = response.json() if response.content else None except ValueError: diff --git a/src/openai/lib/_realtime.py b/src/openai/lib/_realtime.py index 3771b52986..e894d8e451 100644 --- a/src/openai/lib/_realtime.py +++ b/src/openai/lib/_realtime.py @@ -3,7 +3,7 @@ import json from typing_extensions import override -import httpx +import httpx2 from openai import _legacy_response from openai._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -28,7 +28,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: if session is omit: extra_headers = {"Accept": "application/sdp", "Content-Type": "application/sdp", **(extra_headers or {})} @@ -65,7 +65,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: if session is omit: extra_headers = {"Accept": "application/sdp", "Content-Type": "application/sdp", **(extra_headers or {})} diff --git a/src/openai/lib/azure.py b/src/openai/lib/azure.py index 4ebe0a98aa..5e0b6f5203 100644 --- a/src/openai/lib/azure.py +++ b/src/openai/lib/azure.py @@ -5,7 +5,7 @@ from typing import Any, Union, Mapping, TypeVar, Callable, Awaitable, cast, overload from typing_extensions import Self, override -import httpx +import httpx2 from ..auth import WorkloadIdentity from .._types import NOT_GIVEN, Omit, Query, Headers, Timeout, NotGiven @@ -35,7 +35,7 @@ AzureADTokenProvider = Callable[[], str] AsyncAzureADTokenProvider = Callable[[], "str | Awaitable[str]"] -_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient]) +_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx2.Client, httpx2.AsyncClient]) _DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]]) @@ -62,7 +62,7 @@ def __init__(self) -> None: class BaseAzureClient(BaseClient[_HttpxClientT, _DefaultStreamT]): - _azure_endpoint: httpx.URL | None + _azure_endpoint: httpx2.URL | None _azure_deployment: str | None @override @@ -71,7 +71,7 @@ def _build_request( options: FinalRequestOptions, *, retries_taken: int = 0, - ) -> httpx.Request: + ) -> httpx2.Request: if options.url in _deployments_endpoints and is_mapping(options.json_data): model = options.json_data.get("model") if model is not None and "/deployments" not in str(self.base_url.path): @@ -80,13 +80,13 @@ def _build_request( return super()._build_request(options, retries_taken=retries_taken) @override - def _prepare_url(self, url: str) -> httpx.URL: + def _prepare_url(self, url: str) -> httpx2.URL: """Adjust the URL if the client was configured with an Azure endpoint + deployment and the API feature being called is **not** a deployments-based endpoint (i.e. requires /deployments/deployment-name in the URL path). """ if self._azure_deployment and self._azure_endpoint and url not in _deployments_endpoints: - merge_url = httpx.URL(url) + merge_url = httpx2.URL(url) if merge_url.is_relative_url: merge_raw_path = ( self._azure_endpoint.raw_path.rstrip(b"/") + b"/openai/" + merge_url.raw_path.lstrip(b"/") @@ -98,7 +98,7 @@ def _prepare_url(self, url: str) -> httpx.URL: return super()._prepare_url(url) -class AzureOpenAI(BaseAzureClient[httpx.Client, Stream[Any]], OpenAI): +class AzureOpenAI(BaseAzureClient[httpx2.Client, Stream[Any]], OpenAI): @overload def __init__( self, @@ -112,12 +112,12 @@ def __init__( azure_ad_token_provider: AzureADTokenProvider | None = None, organization: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - http_client: httpx.Client | None = None, + http_client: httpx2.Client | None = None, _strict_response_validation: bool = False, _enforce_credentials: bool = True, ) -> None: ... @@ -134,12 +134,12 @@ def __init__( azure_ad_token_provider: AzureADTokenProvider | None = None, organization: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - http_client: httpx.Client | None = None, + http_client: httpx2.Client | None = None, _strict_response_validation: bool = False, _enforce_credentials: bool = True, ) -> None: ... @@ -156,12 +156,12 @@ def __init__( azure_ad_token_provider: AzureADTokenProvider | None = None, organization: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - http_client: httpx.Client | None = None, + http_client: httpx2.Client | None = None, _strict_response_validation: bool = False, _enforce_credentials: bool = True, ) -> None: ... @@ -181,13 +181,13 @@ def __init__( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, base_url: str | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - http_client: httpx.Client | None = None, + http_client: httpx2.Client | None = None, _strict_response_validation: bool = False, _enforce_credentials: bool = True, ) -> None: @@ -276,7 +276,7 @@ def __init__( self._azure_ad_token = azure_ad_token self._azure_ad_token_provider = azure_ad_token_provider self._azure_deployment = azure_deployment if azure_endpoint else None - self._azure_endpoint = httpx.URL(azure_endpoint) if azure_endpoint else None + self._azure_endpoint = httpx2.URL(azure_endpoint) if azure_endpoint else None @override def copy( @@ -289,13 +289,13 @@ def copy( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, api_version: str | None = None, azure_ad_token: str | None = None, azure_ad_token_provider: AzureADTokenProvider | None = None, - base_url: str | httpx.URL | None = None, + base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - http_client: httpx.Client | None = None, + http_client: httpx2.Client | None = None, max_retries: int | NotGiven = NOT_GIVEN, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, @@ -393,7 +393,7 @@ def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions: return options - def _configure_realtime(self, model: str, extra_query: Query) -> tuple[httpx.URL, dict[str, str]]: + def _configure_realtime(self, model: str, extra_query: Query) -> tuple[httpx2.URL, dict[str, str]]: auth_headers = {} query = { **extra_query, @@ -419,7 +419,7 @@ def _configure_realtime(self, model: str, extra_query: Query) -> tuple[httpx.URL return url, auth_headers -class AsyncAzureOpenAI(BaseAzureClient[httpx.AsyncClient, AsyncStream[Any]], AsyncOpenAI): +class AsyncAzureOpenAI(BaseAzureClient[httpx2.AsyncClient, AsyncStream[Any]], AsyncOpenAI): @overload def __init__( self, @@ -434,12 +434,12 @@ def __init__( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - http_client: httpx.AsyncClient | None = None, + http_client: httpx2.AsyncClient | None = None, _strict_response_validation: bool = False, _enforce_credentials: bool = True, ) -> None: ... @@ -457,12 +457,12 @@ def __init__( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - http_client: httpx.AsyncClient | None = None, + http_client: httpx2.AsyncClient | None = None, _strict_response_validation: bool = False, _enforce_credentials: bool = True, ) -> None: ... @@ -480,12 +480,12 @@ def __init__( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - http_client: httpx.AsyncClient | None = None, + http_client: httpx2.AsyncClient | None = None, _strict_response_validation: bool = False, _enforce_credentials: bool = True, ) -> None: ... @@ -506,12 +506,12 @@ def __init__( project: str | None = None, webhook_secret: str | None = None, base_url: str | None = None, - websocket_base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - http_client: httpx.AsyncClient | None = None, + http_client: httpx2.AsyncClient | None = None, _strict_response_validation: bool = False, _enforce_credentials: bool = True, ) -> None: @@ -600,7 +600,7 @@ def __init__( self._azure_ad_token = azure_ad_token self._azure_ad_token_provider = azure_ad_token_provider self._azure_deployment = azure_deployment if azure_endpoint else None - self._azure_endpoint = httpx.URL(azure_endpoint) if azure_endpoint else None + self._azure_endpoint = httpx2.URL(azure_endpoint) if azure_endpoint else None @override def copy( @@ -613,13 +613,13 @@ def copy( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, api_version: str | None = None, azure_ad_token: str | None = None, azure_ad_token_provider: AsyncAzureADTokenProvider | None = None, - base_url: str | httpx.URL | None = None, + base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - http_client: httpx.AsyncClient | None = None, + http_client: httpx2.AsyncClient | None = None, max_retries: int | NotGiven = NOT_GIVEN, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, @@ -719,7 +719,7 @@ async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOp return options - async def _configure_realtime(self, model: str, extra_query: Query) -> tuple[httpx.URL, dict[str, str]]: + async def _configure_realtime(self, model: str, extra_query: Query) -> tuple[httpx2.URL, dict[str, str]]: auth_headers = {} query = { **extra_query, diff --git a/src/openai/lib/bedrock.py b/src/openai/lib/bedrock.py index 466e8ed75e..25a4a56453 100644 --- a/src/openai/lib/bedrock.py +++ b/src/openai/lib/bedrock.py @@ -8,7 +8,7 @@ from dataclasses import field, replace, dataclass from typing_extensions import Self, override -import httpx +import httpx2 from ..auth import WorkloadIdentity from .._types import NOT_GIVEN, Timeout, NotGiven @@ -71,7 +71,7 @@ def _configured_region(region: str | None) -> str | None: return configured.strip() if configured is not None and configured.strip() else None -def _uses_region_derived_base_url(base_url: str | httpx.URL | None) -> bool: +def _uses_region_derived_base_url(base_url: str | httpx2.URL | None) -> bool: if isinstance(base_url, str) and not base_url.strip(): base_url = None if base_url is not None: @@ -121,7 +121,7 @@ def _legacy_provider( aws_secret_access_key: str | None, aws_session_token: str | None, aws_credentials_provider: AwsCredentialsProvider | None, - base_url: str | httpx.URL | None, + base_url: str | httpx2.URL | None, region_was_explicit: bool | None = None, ) -> tuple[_Provider, _LegacyBedrockState, str]: if callable(cast(object, api_key)): @@ -154,7 +154,7 @@ def _legacy_provider( resolved_region = _configured_region(aws_region) uses_region_derived_base_url = _uses_region_derived_base_url(base_url) - provider_base_url: str | httpx.URL | None | NotGiven + provider_base_url: str | httpx2.URL | None | NotGiven if isinstance(base_url, str) and not base_url.strip(): provider_base_url = None elif base_url is None: @@ -203,7 +203,7 @@ def _copy_configuration( aws_secret_access_key: str | None, aws_session_token: str | None, aws_credentials_provider: AwsCredentialsProvider | None, - base_url: str | httpx.URL | None, + base_url: str | httpx2.URL | None, ) -> tuple[dict[str, object], _Provider | None, _LegacyBedrockState | None]: _synchronize_legacy_routing_state(client) state = client._bedrock_state @@ -271,7 +271,7 @@ def _copy_configuration( next_region = None if base_url is not None: - next_base_url: str | httpx.URL | None = base_url + next_base_url: str | httpx2.URL | None = base_url elif state.uses_region_derived_base_url: next_base_url = "" else: @@ -406,13 +406,13 @@ def __init__( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - base_url: str | httpx.URL | None = None, - websocket_base_url: str | httpx.URL | None = None, + base_url: str | httpx2.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - http_client: httpx.Client | None = None, + http_client: httpx2.Client | None = None, _strict_response_validation: bool = False, _enforce_credentials: bool = True, _provider: _Provider | None = None, @@ -525,10 +525,10 @@ def copy( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, - base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, + base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - http_client: httpx.Client | None = None, + http_client: httpx2.Client | None = None, max_retries: int | NotGiven = NOT_GIVEN, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, @@ -640,13 +640,13 @@ def __init__( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - base_url: str | httpx.URL | None = None, - websocket_base_url: str | httpx.URL | None = None, + base_url: str | httpx2.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - http_client: httpx.AsyncClient | None = None, + http_client: httpx2.AsyncClient | None = None, _strict_response_validation: bool = False, _enforce_credentials: bool = True, _provider: _Provider | None = None, @@ -761,10 +761,10 @@ def copy( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - websocket_base_url: str | httpx.URL | None = None, - base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, + base_url: str | httpx2.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - http_client: httpx.AsyncClient | None = None, + http_client: httpx2.AsyncClient | None = None, max_retries: int | NotGiven = NOT_GIVEN, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, diff --git a/src/openai/providers/bedrock.py b/src/openai/providers/bedrock.py index ed6be81c9e..e6dafaeaf2 100644 --- a/src/openai/providers/bedrock.py +++ b/src/openai/providers/bedrock.py @@ -6,7 +6,7 @@ from typing import Literal, Callable, Awaitable, cast from dataclasses import field, dataclass -import httpx +import httpx2 from .._types import NOT_GIVEN, NotGiven from .._utils import asyncify @@ -34,7 +34,7 @@ def _normalize_optional_string(value: str | None) -> str | None: return normalized or None -def _normalize_base_url(base_url: str | httpx.URL) -> httpx.URL: +def _normalize_base_url(base_url: str | httpx2.URL) -> httpx2.URL: url = normalize_httpx_url(base_url) path = url.path.rstrip("/") responses_match = re.search(r"/responses(?:/.*)?$", path) @@ -44,11 +44,11 @@ def _normalize_base_url(base_url: str | httpx.URL) -> httpx.URL: return url.copy_with(path=path or "/") -def _same_origin(left: httpx.URL, right: httpx.URL) -> bool: +def _same_origin(left: httpx2.URL, right: httpx2.URL) -> bool: return (left.scheme, left.host, left.port) == (right.scheme, right.host, right.port) -def _body_for_signing(request: httpx.Request) -> bytes: +def _body_for_signing(request: httpx2.Request) -> bytes: try: return request.content except request_not_read_exceptions() as exc: @@ -58,7 +58,7 @@ def _body_for_signing(request: httpx.Request) -> bytes: ) from exc -def _assert_provider_owns_authorization(request: httpx.Request) -> None: +def _assert_provider_owns_authorization(request: httpx2.Request) -> None: if "Authorization" in request.headers: raise OpenAIError("Bedrock provider authentication cannot be combined with a custom `Authorization` header.") @@ -74,11 +74,11 @@ def _without_redirects(options: FinalRequestOptions) -> FinalRequestOptions: class _BedrockBearerAuth: - def __init__(self, token_provider: BedrockTokenProvider, *, base_url: httpx.URL) -> None: + def __init__(self, token_provider: BedrockTokenProvider, *, base_url: httpx2.URL) -> None: self._token_provider = token_provider self._base_url = base_url - def _validate_request(self, request: httpx.Request) -> None: + def _validate_request(self, request: httpx2.Request) -> None: _assert_provider_owns_authorization(request) if not _same_origin(request.url, self._base_url): raise OpenAIError( @@ -116,11 +116,11 @@ async def _resolve_token_async(self) -> str: raise OpenAIError("The Bedrock bearer credential provider must return a non-empty string.") return token - def prepare_request(self, request: httpx.Request) -> None: + def prepare_request(self, request: httpx2.Request) -> None: self._validate_request(request) request.headers["Authorization"] = f"Bearer {self._resolve_token()}" - async def prepare_async_request(self, request: httpx.Request) -> None: + async def prepare_async_request(self, request: httpx2.Request) -> None: self._validate_request(request) request.headers["Authorization"] = f"Bearer {await self._resolve_token_async()}" @@ -130,14 +130,14 @@ def __init__( self, *, config: BedrockAwsAuthConfig, - base_url: httpx.URL, + base_url: httpx2.URL, auth: BedrockAwsAuth | None = None, ) -> None: self._config = config self._base_url = base_url self._auth = auth - def _validate_request(self, request: httpx.Request) -> bytes: + def _validate_request(self, request: httpx2.Request) -> bytes: _assert_provider_owns_authorization(request) if not _same_origin(request.url, self._base_url): raise OpenAIError( @@ -153,7 +153,7 @@ def _validate_request(self, request: httpx.Request) -> bytes: return _body_for_signing(request) - def _sign(self, request: httpx.Request, *, auth: BedrockAwsAuth, body: bytes) -> None: + def _sign(self, request: httpx2.Request, *, auth: BedrockAwsAuth, body: bytes) -> None: for header in _AWS_SIGNING_HEADERS: request.headers.pop(header, None) @@ -166,13 +166,13 @@ def _sign(self, request: httpx.Request, *, auth: BedrockAwsAuth, body: bytes) -> request.headers.clear() request.headers.update(signed_headers) - def prepare_request(self, request: httpx.Request) -> None: + def prepare_request(self, request: httpx2.Request) -> None: body = self._validate_request(request) if self._auth is None: self._auth = BedrockAwsAuth(self._config) self._sign(request, auth=self._auth, body=body) - async def prepare_async_request(self, request: httpx.Request) -> None: + async def prepare_async_request(self, request: httpx2.Request) -> None: body = self._validate_request(request) if self._auth is None: self._auth = await asyncify(BedrockAwsAuth)(self._config) @@ -198,7 +198,7 @@ class _BedrockProviderRuntime(_ProviderRuntime): class _BedrockProviderDefinition: configured_region: str | None region_source: Literal["explicit", "environment"] | None - configured_base_url: httpx.URL | None + configured_base_url: httpx2.URL | None api_key: str | None = field(default=None, repr=False) token_provider: BedrockTokenProvider | None = field(default=None, repr=False, compare=False) use_environment_bearer: bool = False @@ -309,7 +309,7 @@ def environment_token() -> str: def bedrock( *, region: str | None = None, - base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN, + base_url: str | httpx2.URL | None | NotGiven = NOT_GIVEN, api_key: str | None | NotGiven = NOT_GIVEN, token_provider: BedrockTokenProvider | None = None, access_key_id: str | None = None, @@ -334,7 +334,7 @@ def bedrock( if normalized_region is not None: region_source = "environment" - configured_base_url: httpx.URL | None + configured_base_url: httpx2.URL | None if isinstance(base_url, NotGiven): environment_base_url = _normalize_optional_string(os.environ.get("AWS_BEDROCK_BASE_URL")) configured_base_url = _normalize_base_url(environment_base_url) if environment_base_url else None diff --git a/src/openai/resources/admin/organization/admin_api_keys.py b/src/openai/resources/admin/organization/admin_api_keys.py index b521393f2e..518413e4ee 100644 --- a/src/openai/resources/admin/organization/admin_api_keys.py +++ b/src/openai/resources/admin/organization/admin_api_keys.py @@ -5,7 +5,7 @@ from typing import Optional from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -53,7 +53,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AdminAPIKeyCreateResponse: """ Create an organization admin API key @@ -98,7 +98,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AdminAPIKey: """ Retrieve a single organization API key @@ -139,7 +139,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[AdminAPIKey]: """ List organization API keys @@ -189,7 +189,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AdminAPIKeyDeleteResponse: """ Delete an organization admin API key @@ -250,7 +250,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AdminAPIKeyCreateResponse: """ Create an organization admin API key @@ -295,7 +295,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AdminAPIKey: """ Retrieve a single organization API key @@ -336,7 +336,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[AdminAPIKey, AsyncCursorPage[AdminAPIKey]]: """ List organization API keys @@ -386,7 +386,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AdminAPIKeyDeleteResponse: """ Delete an organization admin API key diff --git a/src/openai/resources/admin/organization/audit_logs.py b/src/openai/resources/admin/organization/audit_logs.py index 9c8ec62543..5fc61feae3 100644 --- a/src/openai/resources/admin/organization/audit_logs.py +++ b/src/openai/resources/admin/organization/audit_logs.py @@ -5,7 +5,7 @@ from typing import List from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given @@ -208,7 +208,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[AuditLogListResponse]: """ List user actions and configuration changes within this organization. @@ -473,7 +473,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[AuditLogListResponse, AsyncConversationCursorPage[AuditLogListResponse]]: """ List user actions and configuration changes within this organization. diff --git a/src/openai/resources/admin/organization/certificates.py b/src/openai/resources/admin/organization/certificates.py index 025c493368..e2f0c08e8f 100644 --- a/src/openai/resources/admin/organization/certificates.py +++ b/src/openai/resources/admin/organization/certificates.py @@ -5,7 +5,7 @@ from typing import List from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given @@ -62,7 +62,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Certificate: """Upload a certificate to the organization. @@ -113,7 +113,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Certificate: """ Get a certificate that has been uploaded to the organization. @@ -157,7 +157,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Certificate: """Modify a certificate. @@ -200,7 +200,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[CertificateListResponse]: """ List uploaded certificates for this organization. @@ -255,7 +255,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> CertificateDeleteResponse: """ Delete a certificate from the organization. @@ -294,7 +294,7 @@ def activate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncPage[CertificateActivateResponse]: """ Activate certificates at the organization level. @@ -336,7 +336,7 @@ def deactivate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncPage[CertificateDeactivateResponse]: """ Deactivate certificates at the organization level. @@ -400,7 +400,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Certificate: """Upload a certificate to the organization. @@ -451,7 +451,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Certificate: """ Get a certificate that has been uploaded to the organization. @@ -497,7 +497,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Certificate: """Modify a certificate. @@ -540,7 +540,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[CertificateListResponse, AsyncConversationCursorPage[CertificateListResponse]]: """ List uploaded certificates for this organization. @@ -595,7 +595,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> CertificateDeleteResponse: """ Delete a certificate from the organization. @@ -634,7 +634,7 @@ def activate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[CertificateActivateResponse, AsyncPage[CertificateActivateResponse]]: """ Activate certificates at the organization level. @@ -676,7 +676,7 @@ def deactivate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[CertificateDeactivateResponse, AsyncPage[CertificateDeactivateResponse]]: """ Deactivate certificates at the organization level. diff --git a/src/openai/resources/admin/organization/data_retention.py b/src/openai/resources/admin/organization/data_retention.py index 1b353c7c18..cb2ff611d6 100644 --- a/src/openai/resources/admin/organization/data_retention.py +++ b/src/openai/resources/admin/organization/data_retention.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Query, Headers, NotGiven, not_given @@ -47,7 +47,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationDataRetention: """Retrieves organization data retention controls.""" return self._get( @@ -76,7 +76,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationDataRetention: """ Updates organization data retention controls. @@ -136,7 +136,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationDataRetention: """Retrieves organization data retention controls.""" return await self._get( @@ -165,7 +165,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationDataRetention: """ Updates organization data retention controls. diff --git a/src/openai/resources/admin/organization/groups/groups.py b/src/openai/resources/admin/organization/groups/groups.py index 7c7f1dc5cd..24e44f4a19 100644 --- a/src/openai/resources/admin/organization/groups/groups.py +++ b/src/openai/resources/admin/organization/groups/groups.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ..... import _legacy_response from .roles import ( @@ -75,7 +75,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Group: """ Creates a new group in the organization. @@ -113,7 +113,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Group: """ Retrieves a group. @@ -151,7 +151,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> GroupUpdateResponse: """ Updates a group's information. @@ -193,7 +193,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncNextCursorPage[Group]: """ Lists all groups in the organization. @@ -247,7 +247,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> GroupDeleteResponse: """ Deletes a group from the organization. @@ -313,7 +313,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Group: """ Creates a new group in the organization. @@ -351,7 +351,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Group: """ Retrieves a group. @@ -389,7 +389,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> GroupUpdateResponse: """ Updates a group's information. @@ -431,7 +431,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Group, AsyncNextCursorPage[Group]]: """ Lists all groups in the organization. @@ -485,7 +485,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> GroupDeleteResponse: """ Deletes a group from the organization. diff --git a/src/openai/resources/admin/organization/groups/roles.py b/src/openai/resources/admin/organization/groups/roles.py index e7054d5f0c..b6f0e85909 100644 --- a/src/openai/resources/admin/organization/groups/roles.py +++ b/src/openai/resources/admin/organization/groups/roles.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -53,7 +53,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleCreateResponse: """ Assigns an organization role to a group within the organization. @@ -94,7 +94,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleRetrieveResponse: """ Retrieves an organization role assigned to a group. @@ -136,7 +136,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncNextCursorPage[RoleListResponse]: """ Lists the organization roles assigned to a group within the organization. @@ -190,7 +190,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleDeleteResponse: """ Unassigns an organization role from a group within the organization. @@ -251,7 +251,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleCreateResponse: """ Assigns an organization role to a group within the organization. @@ -292,7 +292,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleRetrieveResponse: """ Retrieves an organization role assigned to a group. @@ -334,7 +334,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[RoleListResponse, AsyncNextCursorPage[RoleListResponse]]: """ Lists the organization roles assigned to a group within the organization. @@ -388,7 +388,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleDeleteResponse: """ Unassigns an organization role from a group within the organization. diff --git a/src/openai/resources/admin/organization/groups/users.py b/src/openai/resources/admin/organization/groups/users.py index 0e40f772b2..9dd8feb1ca 100644 --- a/src/openai/resources/admin/organization/groups/users.py +++ b/src/openai/resources/admin/organization/groups/users.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -53,7 +53,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UserCreateResponse: """ Adds a user to a group. @@ -94,7 +94,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UserRetrieveResponse: """ Retrieves a user in a group. @@ -136,7 +136,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncNextCursorPage[OrganizationGroupUser]: """ Lists the users assigned to a group. @@ -191,7 +191,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UserDeleteResponse: """ Removes a user from a group. @@ -252,7 +252,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UserCreateResponse: """ Adds a user to a group. @@ -293,7 +293,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UserRetrieveResponse: """ Retrieves a user in a group. @@ -335,7 +335,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[OrganizationGroupUser, AsyncNextCursorPage[OrganizationGroupUser]]: """ Lists the users assigned to a group. @@ -390,7 +390,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UserDeleteResponse: """ Removes a user from a group. diff --git a/src/openai/resources/admin/organization/invites.py b/src/openai/resources/admin/organization/invites.py index b06e4be2aa..cfe63a946a 100644 --- a/src/openai/resources/admin/organization/invites.py +++ b/src/openai/resources/admin/organization/invites.py @@ -5,7 +5,7 @@ from typing import Iterable from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -53,7 +53,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Invite: """Create an invite for a user to the organization. @@ -107,7 +107,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Invite: """ Retrieves an invite. @@ -145,7 +145,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[Invite]: """ Returns a list of invites in the organization. @@ -196,7 +196,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> InviteDeleteResponse: """Delete an invite. @@ -257,7 +257,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Invite: """Create an invite for a user to the organization. @@ -311,7 +311,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Invite: """ Retrieves an invite. @@ -349,7 +349,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Invite, AsyncConversationCursorPage[Invite]]: """ Returns a list of invites in the organization. @@ -400,7 +400,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> InviteDeleteResponse: """Delete an invite. diff --git a/src/openai/resources/admin/organization/projects/api_keys.py b/src/openai/resources/admin/organization/projects/api_keys.py index 0bc16e807c..50ba64f81d 100644 --- a/src/openai/resources/admin/organization/projects/api_keys.py +++ b/src/openai/resources/admin/organization/projects/api_keys.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -51,7 +51,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectAPIKey: """ Retrieves an API key in the project. @@ -97,7 +97,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[ProjectAPIKey]: """ Returns a list of API keys in the project. @@ -158,7 +158,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> APIKeyDeleteResponse: """ Deletes an API key from the project. @@ -226,7 +226,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectAPIKey: """ Retrieves an API key in the project. @@ -272,7 +272,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ProjectAPIKey, AsyncConversationCursorPage[ProjectAPIKey]]: """ Returns a list of API keys in the project. @@ -333,7 +333,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> APIKeyDeleteResponse: """ Deletes an API key from the project. diff --git a/src/openai/resources/admin/organization/projects/certificates.py b/src/openai/resources/admin/organization/projects/certificates.py index 0bd8c02601..c57f1d58f1 100644 --- a/src/openai/resources/admin/organization/projects/certificates.py +++ b/src/openai/resources/admin/organization/projects/certificates.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given @@ -58,7 +58,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[CertificateListResponse]: """ List certificates for this project. @@ -116,7 +116,7 @@ def activate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncPage[CertificateActivateResponse]: """ Activate certificates at the project level. @@ -161,7 +161,7 @@ def deactivate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncPage[CertificateDeactivateResponse]: """Deactivate certificates at the project level. @@ -229,7 +229,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[CertificateListResponse, AsyncConversationCursorPage[CertificateListResponse]]: """ List certificates for this project. @@ -287,7 +287,7 @@ def activate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[CertificateActivateResponse, AsyncPage[CertificateActivateResponse]]: """ Activate certificates at the project level. @@ -332,7 +332,7 @@ def deactivate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[CertificateDeactivateResponse, AsyncPage[CertificateDeactivateResponse]]: """Deactivate certificates at the project level. diff --git a/src/openai/resources/admin/organization/projects/data_retention.py b/src/openai/resources/admin/organization/projects/data_retention.py index 14c9c8c4a0..90ab30d2b9 100644 --- a/src/openai/resources/admin/organization/projects/data_retention.py +++ b/src/openai/resources/admin/organization/projects/data_retention.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Query, Headers, NotGiven, not_given @@ -48,7 +48,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectDataRetention: """ Retrieves project data retention controls. @@ -93,7 +93,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectDataRetention: """ Updates project data retention controls. @@ -156,7 +156,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectDataRetention: """ Retrieves project data retention controls. @@ -201,7 +201,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectDataRetention: """ Updates project data retention controls. diff --git a/src/openai/resources/admin/organization/projects/groups/groups.py b/src/openai/resources/admin/organization/projects/groups/groups.py index 2b8ad6fd55..e86700c26c 100644 --- a/src/openai/resources/admin/organization/projects/groups/groups.py +++ b/src/openai/resources/admin/organization/projects/groups/groups.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ...... import _legacy_response from .roles import ( @@ -64,7 +64,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectGroup: """ Grants a group access to a project. @@ -114,7 +114,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectGroup: """ Retrieves a project's group. @@ -161,7 +161,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncNextCursorPage[ProjectGroup]: """ Lists the groups that have access to a project. @@ -215,7 +215,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> GroupDeleteResponse: """ Revokes a group's access to a project. @@ -283,7 +283,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectGroup: """ Grants a group access to a project. @@ -333,7 +333,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectGroup: """ Retrieves a project's group. @@ -382,7 +382,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ProjectGroup, AsyncNextCursorPage[ProjectGroup]]: """ Lists the groups that have access to a project. @@ -436,7 +436,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> GroupDeleteResponse: """ Revokes a group's access to a project. diff --git a/src/openai/resources/admin/organization/projects/groups/roles.py b/src/openai/resources/admin/organization/projects/groups/roles.py index 70c5e93363..a3f9cff121 100644 --- a/src/openai/resources/admin/organization/projects/groups/roles.py +++ b/src/openai/resources/admin/organization/projects/groups/roles.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ...... import _legacy_response from ......_types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -54,7 +54,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleCreateResponse: """ Assigns a project role to a group within a project. @@ -98,7 +98,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleRetrieveResponse: """ Retrieves a project role assigned to a group. @@ -148,7 +148,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncNextCursorPage[RoleListResponse]: """ Lists the project roles assigned to a group within a project. @@ -205,7 +205,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleDeleteResponse: """ Unassigns a project role from a group within a project. @@ -274,7 +274,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleCreateResponse: """ Assigns a project role to a group within a project. @@ -318,7 +318,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleRetrieveResponse: """ Retrieves a project role assigned to a group. @@ -368,7 +368,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[RoleListResponse, AsyncNextCursorPage[RoleListResponse]]: """ Lists the project roles assigned to a group within a project. @@ -425,7 +425,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleDeleteResponse: """ Unassigns a project role from a group within a project. diff --git a/src/openai/resources/admin/organization/projects/hosted_tool_permissions.py b/src/openai/resources/admin/organization/projects/hosted_tool_permissions.py index 05737e8860..60ad9e830a 100644 --- a/src/openai/resources/admin/organization/projects/hosted_tool_permissions.py +++ b/src/openai/resources/admin/organization/projects/hosted_tool_permissions.py @@ -4,7 +4,7 @@ from typing import Optional -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -48,7 +48,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectHostedToolPermissions: """ Returns hosted tool permissions for a project. @@ -90,7 +90,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectHostedToolPermissions: """ Updates hosted tool permissions for a project. @@ -168,7 +168,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectHostedToolPermissions: """ Returns hosted tool permissions for a project. @@ -210,7 +210,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectHostedToolPermissions: """ Updates hosted tool permissions for a project. diff --git a/src/openai/resources/admin/organization/projects/model_permissions.py b/src/openai/resources/admin/organization/projects/model_permissions.py index 7320109a72..9f9fcb468e 100644 --- a/src/openai/resources/admin/organization/projects/model_permissions.py +++ b/src/openai/resources/admin/organization/projects/model_permissions.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Query, Headers, NotGiven, SequenceNotStr, not_given @@ -49,7 +49,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectModelPermissions: """ Returns model permissions for a project. @@ -88,7 +88,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectModelPermissions: """ Updates model permissions for a project. @@ -136,7 +136,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectModelPermissionsDeleted: """ Deletes model permissions for a project. @@ -194,7 +194,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectModelPermissions: """ Returns model permissions for a project. @@ -233,7 +233,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectModelPermissions: """ Updates model permissions for a project. @@ -281,7 +281,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectModelPermissionsDeleted: """ Deletes model permissions for a project. diff --git a/src/openai/resources/admin/organization/projects/projects.py b/src/openai/resources/admin/organization/projects/projects.py index ea2fb51613..7696f2e9cb 100644 --- a/src/openai/resources/admin/organization/projects/projects.py +++ b/src/openai/resources/admin/organization/projects/projects.py @@ -4,7 +4,7 @@ from typing import Optional -import httpx +import httpx2 from ..... import _legacy_response from .roles import ( @@ -195,7 +195,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Project: """Create a new project in the organization. @@ -249,7 +249,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Project: """ Retrieves a project. @@ -289,7 +289,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Project: """ Modifies a project in the organization. @@ -342,7 +342,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[Project]: """Returns a list of projects. @@ -398,7 +398,7 @@ def archive( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Project: """Archives a project in the organization. @@ -508,7 +508,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Project: """Create a new project in the organization. @@ -562,7 +562,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Project: """ Retrieves a project. @@ -602,7 +602,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Project: """ Modifies a project in the organization. @@ -655,7 +655,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Project, AsyncConversationCursorPage[Project]]: """Returns a list of projects. @@ -711,7 +711,7 @@ async def archive( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Project: """Archives a project in the organization. diff --git a/src/openai/resources/admin/organization/projects/rate_limits.py b/src/openai/resources/admin/organization/projects/rate_limits.py index 7f33d07414..f40ea76cf8 100644 --- a/src/openai/resources/admin/organization/projects/rate_limits.py +++ b/src/openai/resources/admin/organization/projects/rate_limits.py @@ -2,7 +2,7 @@ from __future__ import annotations -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -53,7 +53,7 @@ def list_rate_limits( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[ProjectRateLimit]: """ Returns the rate limits per model for a project. @@ -118,7 +118,7 @@ def update_rate_limit( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectRateLimit: """ Updates a project rate limit. @@ -208,7 +208,7 @@ def list_rate_limits( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ProjectRateLimit, AsyncConversationCursorPage[ProjectRateLimit]]: """ Returns the rate limits per model for a project. @@ -273,7 +273,7 @@ async def update_rate_limit( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectRateLimit: """ Updates a project rate limit. diff --git a/src/openai/resources/admin/organization/projects/roles.py b/src/openai/resources/admin/organization/projects/roles.py index 754a487b52..bb93cfe742 100644 --- a/src/openai/resources/admin/organization/projects/roles.py +++ b/src/openai/resources/admin/organization/projects/roles.py @@ -5,7 +5,7 @@ from typing import Optional from typing_extensions import Literal -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given @@ -54,7 +54,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Role: """ Creates a custom role for a project. @@ -106,7 +106,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Role: """ Retrieves a project role. @@ -149,7 +149,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Role: """ Updates an existing project role. @@ -205,7 +205,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncNextCursorPage[Role]: """Lists the roles configured for a project. @@ -260,7 +260,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleDeleteResponse: """ Deletes a custom role from a project. @@ -323,7 +323,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Role: """ Creates a custom role for a project. @@ -375,7 +375,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Role: """ Retrieves a project role. @@ -418,7 +418,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Role: """ Updates an existing project role. @@ -474,7 +474,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Role, AsyncNextCursorPage[Role]]: """Lists the roles configured for a project. @@ -529,7 +529,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleDeleteResponse: """ Deletes a custom role from a project. diff --git a/src/openai/resources/admin/organization/projects/service_accounts/api_keys.py b/src/openai/resources/admin/organization/projects/service_accounts/api_keys.py index d25b1e8bf0..96f38b544d 100644 --- a/src/openai/resources/admin/organization/projects/service_accounts/api_keys.py +++ b/src/openai/resources/admin/organization/projects/service_accounts/api_keys.py @@ -2,7 +2,7 @@ from __future__ import annotations -import httpx +import httpx2 from ...... import _legacy_response from ......_types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given @@ -49,7 +49,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> APIKeyCreateResponse: """ Creates an API key for a service account in the project. @@ -131,7 +131,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> APIKeyCreateResponse: """ Creates an API key for a service account in the project. diff --git a/src/openai/resources/admin/organization/projects/service_accounts/service_accounts.py b/src/openai/resources/admin/organization/projects/service_accounts/service_accounts.py index e7aa6f0f4e..c1618d4ae6 100644 --- a/src/openai/resources/admin/organization/projects/service_accounts/service_accounts.py +++ b/src/openai/resources/admin/organization/projects/service_accounts/service_accounts.py @@ -5,7 +5,7 @@ from typing import Optional from typing_extensions import Literal -import httpx +import httpx2 from ...... import _legacy_response from .api_keys import ( @@ -70,7 +70,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ServiceAccountCreateResponse: """Creates a new service account in the project. @@ -121,7 +121,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectServiceAccount: """ Retrieves a service account in the project. @@ -167,7 +167,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectServiceAccount: """ Updates a service account in the project. @@ -223,7 +223,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[ProjectServiceAccount]: """ Returns a list of service accounts in the project. @@ -277,7 +277,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ServiceAccountDeleteResponse: """ Deletes a service account from the project. @@ -350,7 +350,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ServiceAccountCreateResponse: """Creates a new service account in the project. @@ -401,7 +401,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectServiceAccount: """ Retrieves a service account in the project. @@ -447,7 +447,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectServiceAccount: """ Updates a service account in the project. @@ -503,7 +503,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ProjectServiceAccount, AsyncConversationCursorPage[ProjectServiceAccount]]: """ Returns a list of service accounts in the project. @@ -557,7 +557,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ServiceAccountDeleteResponse: """ Deletes a service account from the project. diff --git a/src/openai/resources/admin/organization/projects/spend_alerts.py b/src/openai/resources/admin/organization/projects/spend_alerts.py index be0ed1bae6..3fab082ed7 100644 --- a/src/openai/resources/admin/organization/projects/spend_alerts.py +++ b/src/openai/resources/admin/organization/projects/spend_alerts.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -58,7 +58,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendAlert: """ Creates a project spend alert. @@ -113,7 +113,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendAlert: """ Retrieves a project spend alert. @@ -159,7 +159,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendAlert: """ Updates a project spend alert. @@ -221,7 +221,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[ProjectSpendAlert]: """Lists project spend alerts. @@ -280,7 +280,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendAlertDeleted: """ Deletes a project spend alert. @@ -346,7 +346,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendAlert: """ Creates a project spend alert. @@ -401,7 +401,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendAlert: """ Retrieves a project spend alert. @@ -447,7 +447,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendAlert: """ Updates a project spend alert. @@ -509,7 +509,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ProjectSpendAlert, AsyncConversationCursorPage[ProjectSpendAlert]]: """Lists project spend alerts. @@ -568,7 +568,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendAlertDeleted: """ Deletes a project spend alert. diff --git a/src/openai/resources/admin/organization/projects/spend_limit.py b/src/openai/resources/admin/organization/projects/spend_limit.py index 41497b2838..8eec885a08 100644 --- a/src/openai/resources/admin/organization/projects/spend_limit.py +++ b/src/openai/resources/admin/organization/projects/spend_limit.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Query, Headers, NotGiven, not_given @@ -49,7 +49,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendLimit: """ Get a project's hard spend limit. @@ -89,7 +89,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendLimit: """ Create or replace a project's hard spend limit. @@ -141,7 +141,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendLimitDeleted: """ Delete a project's hard spend limit. @@ -199,7 +199,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendLimit: """ Get a project's hard spend limit. @@ -239,7 +239,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendLimit: """ Create or replace a project's hard spend limit. @@ -291,7 +291,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectSpendLimitDeleted: """ Delete a project's hard spend limit. diff --git a/src/openai/resources/admin/organization/projects/users/roles.py b/src/openai/resources/admin/organization/projects/users/roles.py index 59ee68f135..d7ede48ec5 100644 --- a/src/openai/resources/admin/organization/projects/users/roles.py +++ b/src/openai/resources/admin/organization/projects/users/roles.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ...... import _legacy_response from ......_types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -54,7 +54,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleCreateResponse: """ Assigns a project role to a user within a project. @@ -98,7 +98,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleRetrieveResponse: """ Retrieves a project role assigned to a user. @@ -148,7 +148,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncNextCursorPage[RoleListResponse]: """ Lists the project roles assigned to a user within a project. @@ -205,7 +205,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleDeleteResponse: """ Unassigns a project role from a user within a project. @@ -274,7 +274,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleCreateResponse: """ Assigns a project role to a user within a project. @@ -318,7 +318,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleRetrieveResponse: """ Retrieves a project role assigned to a user. @@ -368,7 +368,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[RoleListResponse, AsyncNextCursorPage[RoleListResponse]]: """ Lists the project roles assigned to a user within a project. @@ -425,7 +425,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleDeleteResponse: """ Unassigns a project role from a user within a project. diff --git a/src/openai/resources/admin/organization/projects/users/users.py b/src/openai/resources/admin/organization/projects/users/users.py index a1633968df..e714b86196 100644 --- a/src/openai/resources/admin/organization/projects/users/users.py +++ b/src/openai/resources/admin/organization/projects/users/users.py @@ -4,7 +4,7 @@ from typing import Optional -import httpx +import httpx2 from ...... import _legacy_response from .roles import ( @@ -65,7 +65,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectUser: """Adds a user to the project. @@ -119,7 +119,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectUser: """ Retrieves a user in the project. @@ -162,7 +162,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectUser: """ Modifies a user's role in the project. @@ -208,7 +208,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[ProjectUser]: """ Returns a list of users in the project. @@ -262,7 +262,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UserDeleteResponse: """ Deletes a user from the project. @@ -334,7 +334,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectUser: """Adds a user to the project. @@ -388,7 +388,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectUser: """ Retrieves a user in the project. @@ -431,7 +431,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ProjectUser: """ Modifies a user's role in the project. @@ -477,7 +477,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ProjectUser, AsyncConversationCursorPage[ProjectUser]]: """ Returns a list of users in the project. @@ -531,7 +531,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UserDeleteResponse: """ Deletes a user from the project. diff --git a/src/openai/resources/admin/organization/roles.py b/src/openai/resources/admin/organization/roles.py index aa4f19f504..5ecd2f7626 100644 --- a/src/openai/resources/admin/organization/roles.py +++ b/src/openai/resources/admin/organization/roles.py @@ -5,7 +5,7 @@ from typing import Optional from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given @@ -53,7 +53,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Role: """ Creates a custom role for the organization. @@ -102,7 +102,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Role: """ Retrieves an organization role. @@ -142,7 +142,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Role: """ Updates an existing organization role. @@ -195,7 +195,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncNextCursorPage[Role]: """ Lists the roles configured for the organization. @@ -246,7 +246,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleDeleteResponse: """ Deletes a custom role from the organization. @@ -306,7 +306,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Role: """ Creates a custom role for the organization. @@ -355,7 +355,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Role: """ Retrieves an organization role. @@ -395,7 +395,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Role: """ Updates an existing organization role. @@ -448,7 +448,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Role, AsyncNextCursorPage[Role]]: """ Lists the roles configured for the organization. @@ -499,7 +499,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleDeleteResponse: """ Deletes a custom role from the organization. diff --git a/src/openai/resources/admin/organization/spend_alerts.py b/src/openai/resources/admin/organization/spend_alerts.py index fc36554617..8b601835f7 100644 --- a/src/openai/resources/admin/organization/spend_alerts.py +++ b/src/openai/resources/admin/organization/spend_alerts.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -53,7 +53,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendAlert: """ Creates an organization spend alert. @@ -105,7 +105,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendAlert: """ Retrieves an organization spend alert. @@ -146,7 +146,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendAlert: """ Updates an organization spend alert. @@ -203,7 +203,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[OrganizationSpendAlert]: """Lists organization spend alerts. @@ -259,7 +259,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendAlertDeleted: """ Deletes an organization spend alert. @@ -320,7 +320,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendAlert: """ Creates an organization spend alert. @@ -372,7 +372,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendAlert: """ Retrieves an organization spend alert. @@ -413,7 +413,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendAlert: """ Updates an organization spend alert. @@ -470,7 +470,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[OrganizationSpendAlert, AsyncConversationCursorPage[OrganizationSpendAlert]]: """Lists organization spend alerts. @@ -526,7 +526,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendAlertDeleted: """ Deletes an organization spend alert. diff --git a/src/openai/resources/admin/organization/spend_limit.py b/src/openai/resources/admin/organization/spend_limit.py index b7f2745d2a..fd79775853 100644 --- a/src/openai/resources/admin/organization/spend_limit.py +++ b/src/openai/resources/admin/organization/spend_limit.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Query, Headers, NotGiven, not_given @@ -48,7 +48,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendLimit: """Get the organization's hard spend limit.""" return self._get( @@ -74,7 +74,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendLimit: """ Create or replace the organization's hard spend limit. @@ -123,7 +123,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendLimitDeleted: """Delete the organization's hard spend limit.""" return self._delete( @@ -167,7 +167,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendLimit: """Get the organization's hard spend limit.""" return await self._get( @@ -193,7 +193,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendLimit: """ Create or replace the organization's hard spend limit. @@ -242,7 +242,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationSpendLimitDeleted: """Delete the organization's hard spend limit.""" return await self._delete( diff --git a/src/openai/resources/admin/organization/usage.py b/src/openai/resources/admin/organization/usage.py index 5c2f2b5947..5dbda76f3c 100644 --- a/src/openai/resources/admin/organization/usage.py +++ b/src/openai/resources/admin/organization/usage.py @@ -5,7 +5,7 @@ from typing import List from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given @@ -80,7 +80,7 @@ def audio_speeches( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageAudioSpeechesResponse: """ Get audio speeches usage details for the organization. @@ -166,7 +166,7 @@ def audio_transcriptions( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageAudioTranscriptionsResponse: """ Get audio transcriptions usage details for the organization. @@ -249,7 +249,7 @@ def code_interpreter_sessions( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageCodeInterpreterSessionsResponse: """ Get code interpreter sessions usage details for the organization. @@ -327,7 +327,7 @@ def completions( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageCompletionsResponse: """ Get completions usage details for the organization. @@ -416,7 +416,7 @@ def costs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageCostsResponse: """ Get costs details for the organization. @@ -493,7 +493,7 @@ def embeddings( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageEmbeddingsResponse: """ Get embeddings usage details for the organization. @@ -579,7 +579,7 @@ def file_search_calls( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageFileSearchCallsResponse: """ Get file search calls usage details for the organization. @@ -668,7 +668,7 @@ def images( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageImagesResponse: """ Get images usage details for the organization. @@ -763,7 +763,7 @@ def moderations( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageModerationsResponse: """ Get moderations usage details for the organization. @@ -846,7 +846,7 @@ def vector_stores( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageVectorStoresResponse: """ Get vector stores usage details for the organization. @@ -924,7 +924,7 @@ def web_search_calls( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageWebSearchCallsResponse: """ Get web search calls usage details for the organization. @@ -1035,7 +1035,7 @@ async def audio_speeches( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageAudioSpeechesResponse: """ Get audio speeches usage details for the organization. @@ -1121,7 +1121,7 @@ async def audio_transcriptions( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageAudioTranscriptionsResponse: """ Get audio transcriptions usage details for the organization. @@ -1204,7 +1204,7 @@ async def code_interpreter_sessions( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageCodeInterpreterSessionsResponse: """ Get code interpreter sessions usage details for the organization. @@ -1282,7 +1282,7 @@ async def completions( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageCompletionsResponse: """ Get completions usage details for the organization. @@ -1371,7 +1371,7 @@ async def costs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageCostsResponse: """ Get costs details for the organization. @@ -1448,7 +1448,7 @@ async def embeddings( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageEmbeddingsResponse: """ Get embeddings usage details for the organization. @@ -1534,7 +1534,7 @@ async def file_search_calls( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageFileSearchCallsResponse: """ Get file search calls usage details for the organization. @@ -1623,7 +1623,7 @@ async def images( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageImagesResponse: """ Get images usage details for the organization. @@ -1718,7 +1718,7 @@ async def moderations( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageModerationsResponse: """ Get moderations usage details for the organization. @@ -1801,7 +1801,7 @@ async def vector_stores( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageVectorStoresResponse: """ Get vector stores usage details for the organization. @@ -1879,7 +1879,7 @@ async def web_search_calls( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UsageWebSearchCallsResponse: """ Get web search calls usage details for the organization. diff --git a/src/openai/resources/admin/organization/users/roles.py b/src/openai/resources/admin/organization/users/roles.py index 5baff40973..bc9304e4ec 100644 --- a/src/openai/resources/admin/organization/users/roles.py +++ b/src/openai/resources/admin/organization/users/roles.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -53,7 +53,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleCreateResponse: """ Assigns an organization role to a user within the organization. @@ -94,7 +94,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleRetrieveResponse: """ Retrieves an organization role assigned to a user. @@ -136,7 +136,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncNextCursorPage[RoleListResponse]: """ Lists the organization roles assigned to a user within the organization. @@ -190,7 +190,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleDeleteResponse: """ Unassigns an organization role from a user within the organization. @@ -251,7 +251,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleCreateResponse: """ Assigns an organization role to a user within the organization. @@ -292,7 +292,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleRetrieveResponse: """ Retrieves an organization role assigned to a user. @@ -334,7 +334,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[RoleListResponse, AsyncNextCursorPage[RoleListResponse]]: """ Lists the organization roles assigned to a user within the organization. @@ -388,7 +388,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RoleDeleteResponse: """ Unassigns an organization role from a user within the organization. diff --git a/src/openai/resources/admin/organization/users/users.py b/src/openai/resources/admin/organization/users/users.py index 5ee162a903..4a66571663 100644 --- a/src/openai/resources/admin/organization/users/users.py +++ b/src/openai/resources/admin/organization/users/users.py @@ -4,7 +4,7 @@ from typing import Optional -import httpx +import httpx2 from ..... import _legacy_response from .roles import ( @@ -62,7 +62,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationUser: """ Retrieves a user by their identifier. @@ -103,7 +103,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationUser: """ Modifies a user's role in the organization. @@ -159,7 +159,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[OrganizationUser]: """ Lists all of the users in the organization. @@ -213,7 +213,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UserDeleteResponse: """ Deletes a user from the organization. @@ -275,7 +275,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationUser: """ Retrieves a user by their identifier. @@ -316,7 +316,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OrganizationUser: """ Modifies a user's role in the organization. @@ -372,7 +372,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[OrganizationUser, AsyncConversationCursorPage[OrganizationUser]]: """ Lists all of the users in the organization. @@ -426,7 +426,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UserDeleteResponse: """ Deletes a user from the organization. diff --git a/src/openai/resources/audio/speech.py b/src/openai/resources/audio/speech.py index 91ac32ab96..d0b255b4fb 100644 --- a/src/openai/resources/audio/speech.py +++ b/src/openai/resources/audio/speech.py @@ -5,7 +5,7 @@ from typing import Union from typing_extensions import Literal -import httpx +import httpx2 from ... import _legacy_response from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -62,7 +62,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Generates audio from the input text. @@ -166,7 +166,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Generates audio from the input text. diff --git a/src/openai/resources/audio/transcriptions.py b/src/openai/resources/audio/transcriptions.py index b7f3a9ba35..80b7cbdcf6 100644 --- a/src/openai/resources/audio/transcriptions.py +++ b/src/openai/resources/audio/transcriptions.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, List, Union, Mapping, Optional, cast from typing_extensions import Literal, overload, assert_never -import httpx +import httpx2 from ... import _legacy_response from ..._files import deepcopy_with_paths @@ -85,7 +85,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Transcription: """ Transcribes audio into the input language. @@ -180,7 +180,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> TranscriptionVerbose: ... @overload @@ -203,7 +203,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> str: ... @overload @@ -226,7 +226,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> TranscriptionDiarized: ... @overload @@ -252,7 +252,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Stream[TranscriptionStreamEvent]: """ Transcribes audio into the input language. @@ -373,7 +373,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> TranscriptionCreateResponse | Stream[TranscriptionStreamEvent]: """ Transcribes audio into the input language. @@ -494,7 +494,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> str | Transcription | TranscriptionDiarized | TranscriptionVerbose | Stream[TranscriptionStreamEvent]: body = deepcopy_with_paths( { @@ -587,7 +587,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> TranscriptionCreateResponse: """ Transcribes audio into the input language. @@ -700,7 +700,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> TranscriptionVerbose: ... @overload @@ -723,7 +723,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> str: ... @overload @@ -749,7 +749,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncStream[TranscriptionStreamEvent]: """ Transcribes audio into the input language. @@ -870,7 +870,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> TranscriptionCreateResponse | AsyncStream[TranscriptionStreamEvent]: """ Transcribes audio into the input language. @@ -991,7 +991,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Transcription | TranscriptionVerbose | TranscriptionDiarized | str | AsyncStream[TranscriptionStreamEvent]: body = deepcopy_with_paths( { diff --git a/src/openai/resources/audio/translations.py b/src/openai/resources/audio/translations.py index 8b8f16d051..60b746b5f2 100644 --- a/src/openai/resources/audio/translations.py +++ b/src/openai/resources/audio/translations.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Union, Mapping, cast from typing_extensions import Literal, overload, assert_never -import httpx +import httpx2 from ... import _legacy_response from ..._files import deepcopy_with_paths @@ -63,7 +63,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Translation: ... @overload @@ -80,7 +80,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> TranslationVerbose: ... @overload @@ -97,7 +97,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> str: ... def create( @@ -113,7 +113,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Translation | TranslationVerbose | str: """ Translates audio into English. @@ -215,7 +215,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Translation: ... @overload @@ -232,7 +232,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> TranslationVerbose: ... @overload @@ -249,7 +249,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> str: ... async def create( @@ -265,7 +265,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Translation | TranslationVerbose | str: """ Translates audio into English. diff --git a/src/openai/resources/batches.py b/src/openai/resources/batches.py index 08b3754ed7..9e7381a30f 100644 --- a/src/openai/resources/batches.py +++ b/src/openai/resources/batches.py @@ -5,7 +5,7 @@ from typing import Optional from typing_extensions import Literal -import httpx +import httpx2 from .. import _legacy_response from ..types import batch_list_params, batch_create_params @@ -66,7 +66,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Batch: """ Creates and executes a batch from an uploaded file of requests @@ -141,7 +141,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Batch: """ Retrieves a batch. @@ -179,7 +179,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Batch]: """List your organization's batches. @@ -231,7 +231,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Batch: """Cancels an in-progress batch. @@ -307,7 +307,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Batch: """ Creates and executes a batch from an uploaded file of requests @@ -382,7 +382,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Batch: """ Retrieves a batch. @@ -420,7 +420,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Batch, AsyncCursorPage[Batch]]: """List your organization's batches. @@ -472,7 +472,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Batch: """Cancels an in-progress batch. diff --git a/src/openai/resources/beta/assistants.py b/src/openai/resources/beta/assistants.py index ea536d29e6..6c67501a07 100644 --- a/src/openai/resources/beta/assistants.py +++ b/src/openai/resources/beta/assistants.py @@ -6,7 +6,7 @@ from typing import Union, Iterable, Optional from typing_extensions import Literal -import httpx +import httpx2 from ... import _legacy_response from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -74,7 +74,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Assistant: """ Create an assistant with a model and instructions. @@ -194,7 +194,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Assistant: """ Retrieves an assistant. @@ -291,7 +291,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Assistant: """Modifies an assistant. @@ -417,7 +417,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Assistant]: """Returns a list of assistants. @@ -481,7 +481,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AssistantDeleted: """ Delete an assistant. @@ -553,7 +553,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Assistant: """ Create an assistant with a model and instructions. @@ -673,7 +673,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Assistant: """ Retrieves an assistant. @@ -770,7 +770,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Assistant: """Modifies an assistant. @@ -896,7 +896,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Assistant, AsyncCursorPage[Assistant]]: """Returns a list of assistants. @@ -960,7 +960,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AssistantDeleted: """ Delete an assistant. diff --git a/src/openai/resources/beta/chatkit/sessions.py b/src/openai/resources/beta/chatkit/sessions.py index 41906ad7f7..6b02637bec 100644 --- a/src/openai/resources/beta/chatkit/sessions.py +++ b/src/openai/resources/beta/chatkit/sessions.py @@ -2,7 +2,7 @@ from __future__ import annotations -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -60,7 +60,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatSession: """ Create a ChatKit session. @@ -118,7 +118,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatSession: """ Cancel an active ChatKit session and return its most recent metadata. @@ -183,7 +183,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatSession: """ Create a ChatKit session. @@ -241,7 +241,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatSession: """ Cancel an active ChatKit session and return its most recent metadata. diff --git a/src/openai/resources/beta/chatkit/threads.py b/src/openai/resources/beta/chatkit/threads.py index 42d17a64d5..28c493a7eb 100644 --- a/src/openai/resources/beta/chatkit/threads.py +++ b/src/openai/resources/beta/chatkit/threads.py @@ -5,7 +5,7 @@ from typing import Any, cast from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -52,7 +52,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatKitThread: """ Retrieve a ChatKit thread by its identifier. @@ -94,7 +94,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[ChatKitThread]: """ List ChatKit threads with optional pagination and user filters. @@ -154,7 +154,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ThreadDeleteResponse: """ Delete a ChatKit thread along with its items and stored attachments. @@ -196,7 +196,7 @@ def list_items( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[Data]: """ List items that belong to a ChatKit thread. @@ -275,7 +275,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatKitThread: """ Retrieve a ChatKit thread by its identifier. @@ -317,7 +317,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ChatKitThread, AsyncConversationCursorPage[ChatKitThread]]: """ List ChatKit threads with optional pagination and user filters. @@ -377,7 +377,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ThreadDeleteResponse: """ Delete a ChatKit thread along with its items and stored attachments. @@ -419,7 +419,7 @@ def list_items( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Data, AsyncConversationCursorPage[Data]]: """ List items that belong to a ChatKit thread. diff --git a/src/openai/resources/beta/realtime/realtime.py b/src/openai/resources/beta/realtime/realtime.py index 98b717a184..6d1ef956ed 100644 --- a/src/openai/resources/beta/realtime/realtime.py +++ b/src/openai/resources/beta/realtime/realtime.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any, Iterator, cast from typing_extensions import AsyncIterator -import httpx +import httpx2 from pydantic import BaseModel from .sessions import ( @@ -394,7 +394,7 @@ async def __aenter__(self) -> AsyncRealtimeConnection: enter = __aenter__ - def _prepare_url(self) -> httpx.URL: + def _prepare_url(self) -> httpx2.URL: if self.__client.websocket_base_url is not None: base_url = normalize_httpx_url(self.__client.websocket_base_url) else: @@ -577,7 +577,7 @@ def __enter__(self) -> RealtimeConnection: enter = __enter__ - def _prepare_url(self) -> httpx.URL: + def _prepare_url(self) -> httpx2.URL: if self.__client.websocket_base_url is not None: base_url = normalize_httpx_url(self.__client.websocket_base_url) else: diff --git a/src/openai/resources/beta/realtime/sessions.py b/src/openai/resources/beta/realtime/sessions.py index 9b85e02d17..50a38df7f8 100644 --- a/src/openai/resources/beta/realtime/sessions.py +++ b/src/openai/resources/beta/realtime/sessions.py @@ -5,7 +5,7 @@ from typing import List, Union, Iterable from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven @@ -75,7 +75,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> SessionCreateResponse: """ Create an ephemeral API token for use in client-side applications with the @@ -259,7 +259,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> SessionCreateResponse: """ Create an ephemeral API token for use in client-side applications with the diff --git a/src/openai/resources/beta/realtime/transcription_sessions.py b/src/openai/resources/beta/realtime/transcription_sessions.py index 54fe7d5a6c..5ba79c7172 100644 --- a/src/openai/resources/beta/realtime/transcription_sessions.py +++ b/src/openai/resources/beta/realtime/transcription_sessions.py @@ -5,7 +5,7 @@ from typing import List from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven @@ -56,7 +56,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> TranscriptionSession: """ Create an ephemeral API token for use in client-side applications with the @@ -169,7 +169,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> TranscriptionSession: """ Create an ephemeral API token for use in client-side applications with the diff --git a/src/openai/resources/beta/responses/input_items.py b/src/openai/resources/beta/responses/input_items.py index 325395dac4..219ea5cdd1 100644 --- a/src/openai/resources/beta/responses/input_items.py +++ b/src/openai/resources/beta/responses/input_items.py @@ -5,7 +5,7 @@ from typing import Any, List, cast from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -56,7 +56,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[BetaResponseItem]: """ Returns a list of input items for a given response. @@ -146,7 +146,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[BetaResponseItem, AsyncCursorPage[BetaResponseItem]]: """ Returns a list of input items for a given response. diff --git a/src/openai/resources/beta/responses/input_tokens.py b/src/openai/resources/beta/responses/input_tokens.py index a2af998fbb..cac50b1629 100644 --- a/src/openai/resources/beta/responses/input_tokens.py +++ b/src/openai/resources/beta/responses/input_tokens.py @@ -5,7 +5,7 @@ from typing import List, Union, Iterable, Optional from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -63,7 +63,7 @@ def count( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> InputTokenCountResponse: """ Returns input token counts of the request. @@ -204,7 +204,7 @@ async def count( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> InputTokenCountResponse: """ Returns input token counts of the request. diff --git a/src/openai/resources/beta/responses/responses.py b/src/openai/resources/beta/responses/responses.py index 7bf83d2a4c..2b4217db8f 100644 --- a/src/openai/resources/beta/responses/responses.py +++ b/src/openai/resources/beta/responses/responses.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Union, Callable, Iterable, Iterator, Optional, Awaitable, cast from typing_extensions import Literal, AsyncIterator, overload -import httpx +import httpx2 from pydantic import BaseModel from .... import _legacy_response @@ -249,7 +249,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse: """Creates a model response. @@ -638,7 +638,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Stream[BetaResponseStreamEvent]: """Creates a model response. @@ -1027,7 +1027,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse | Stream[BetaResponseStreamEvent]: """Creates a model response. @@ -1415,7 +1415,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse | Stream[BetaResponseStreamEvent]: extra_headers = { **strip_not_given({"openai-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}), @@ -1489,7 +1489,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse: """ Retrieves a model response with the given ID. @@ -1539,7 +1539,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Stream[BetaResponseStreamEvent]: """ Retrieves a model response with the given ID. @@ -1589,7 +1589,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse | Stream[BetaResponseStreamEvent]: """ Retrieves a model response with the given ID. @@ -1638,7 +1638,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse | Stream[BetaResponseStreamEvent]: if not response_id: raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}") @@ -1679,7 +1679,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Deletes a model response with the given ID. @@ -1722,7 +1722,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse: """Cancels a model response with the given ID. @@ -1878,7 +1878,7 @@ def compact( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaCompactedResponse: """Compact a conversation. @@ -2177,7 +2177,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse: """Creates a model response. @@ -2566,7 +2566,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncStream[BetaResponseStreamEvent]: """Creates a model response. @@ -2955,7 +2955,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse | AsyncStream[BetaResponseStreamEvent]: """Creates a model response. @@ -3343,7 +3343,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse | AsyncStream[BetaResponseStreamEvent]: extra_headers = { **strip_not_given({"openai-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given}), @@ -3417,7 +3417,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse: """ Retrieves a model response with the given ID. @@ -3467,7 +3467,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncStream[BetaResponseStreamEvent]: """ Retrieves a model response with the given ID. @@ -3517,7 +3517,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse | AsyncStream[BetaResponseStreamEvent]: """ Retrieves a model response with the given ID. @@ -3566,7 +3566,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse | AsyncStream[BetaResponseStreamEvent]: if not response_id: raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}") @@ -3607,7 +3607,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Deletes a model response with the given ID. @@ -3650,7 +3650,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaResponse: """Cancels a model response with the given ID. @@ -3806,7 +3806,7 @@ async def compact( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> BetaCompactedResponse: """Compact a conversation. @@ -4486,7 +4486,7 @@ async def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> Async **self.__websocket_connection_options, ) - def _prepare_url(self) -> httpx.URL: + def _prepare_url(self) -> httpx2.URL: if self.__client.websocket_base_url is not None: base_url = normalize_httpx_url(self.__client.websocket_base_url) else: @@ -4931,7 +4931,7 @@ def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> WebSocketCo **self.__websocket_connection_options, ) - def _prepare_url(self) -> httpx.URL: + def _prepare_url(self) -> httpx2.URL: if self.__client.websocket_base_url is not None: base_url = normalize_httpx_url(self.__client.websocket_base_url) else: diff --git a/src/openai/resources/beta/threads/messages.py b/src/openai/resources/beta/threads/messages.py index c02e3191fd..66f57f9488 100644 --- a/src/openai/resources/beta/threads/messages.py +++ b/src/openai/resources/beta/threads/messages.py @@ -6,7 +6,7 @@ from typing import Union, Iterable, Optional from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -64,7 +64,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Message: """ Create a message. @@ -132,7 +132,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Message: """ Retrieve a message. @@ -175,7 +175,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Message: """ Modifies a message. @@ -229,7 +229,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Message]: """ Returns a list of messages for a given thread. @@ -298,7 +298,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> MessageDeleted: """ Deletes a message. @@ -366,7 +366,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Message: """ Create a message. @@ -434,7 +434,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Message: """ Retrieve a message. @@ -477,7 +477,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Message: """ Modifies a message. @@ -531,7 +531,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Message, AsyncCursorPage[Message]]: """ Returns a list of messages for a given thread. @@ -600,7 +600,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> MessageDeleted: """ Deletes a message. diff --git a/src/openai/resources/beta/threads/runs/runs.py b/src/openai/resources/beta/threads/runs/runs.py index f1b00f8ede..5321b02c5a 100644 --- a/src/openai/resources/beta/threads/runs/runs.py +++ b/src/openai/resources/beta/threads/runs/runs.py @@ -7,7 +7,7 @@ from functools import partial from typing_extensions import Literal, overload -import httpx +import httpx2 from ..... import _legacy_response from .steps import ( @@ -115,7 +115,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run: """ Create a run. @@ -266,7 +266,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Stream[AssistantStreamEvent]: """ Create a run. @@ -417,7 +417,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run | Stream[AssistantStreamEvent]: """ Create a run. @@ -568,7 +568,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run | Stream[AssistantStreamEvent]: if not thread_id: raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") @@ -622,7 +622,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run: """ Retrieves a run. @@ -665,7 +665,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run: """ Modifies a run. @@ -718,7 +718,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Run]: """ Returns a list of runs belonging to a thread. @@ -784,7 +784,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run: """ Cancels a run that is `in_progress`. @@ -843,7 +843,7 @@ def create_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> Run: """ A helper to create a run an poll for a terminal state. More information on Run @@ -913,7 +913,7 @@ def create_and_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AssistantStreamManager[AssistantEventHandler]: """Create a Run stream""" ... @@ -946,7 +946,7 @@ def create_and_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AssistantStreamManager[AssistantEventHandlerT]: """Create a Run stream""" ... @@ -978,7 +978,7 @@ def create_and_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AssistantStreamManager[AssistantEventHandler] | AssistantStreamManager[AssistantEventHandlerT]: """Create a Run stream""" if not thread_id: @@ -1036,7 +1036,7 @@ def poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, poll_interval_ms: int | Omit = omit, ) -> Run: """ @@ -1102,7 +1102,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AssistantStreamManager[AssistantEventHandler]: """Create a Run stream""" ... @@ -1136,7 +1136,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AssistantStreamManager[AssistantEventHandlerT]: """Create a Run stream""" ... @@ -1169,7 +1169,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AssistantStreamManager[AssistantEventHandler] | AssistantStreamManager[AssistantEventHandlerT]: """Create a Run stream""" if not thread_id: @@ -1234,7 +1234,7 @@ def submit_tool_outputs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run: """ When a run has the `status: "requires_action"` and `required_action.type` is @@ -1273,7 +1273,7 @@ def submit_tool_outputs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Stream[AssistantStreamEvent]: """ When a run has the `status: "requires_action"` and `required_action.type` is @@ -1312,7 +1312,7 @@ def submit_tool_outputs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run | Stream[AssistantStreamEvent]: """ When a run has the `status: "requires_action"` and `required_action.type` is @@ -1352,7 +1352,7 @@ def submit_tool_outputs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run | Stream[AssistantStreamEvent]: if not thread_id: raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") @@ -1396,7 +1396,7 @@ def submit_tool_outputs_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> Run: """ A helper to submit a tool output to a run and poll for a terminal run state. @@ -1436,7 +1436,7 @@ def submit_tool_outputs_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AssistantStreamManager[AssistantEventHandler]: """ Submit the tool outputs from a previous run and stream the run to a terminal @@ -1459,7 +1459,7 @@ def submit_tool_outputs_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AssistantStreamManager[AssistantEventHandlerT]: """ Submit the tool outputs from a previous run and stream the run to a terminal @@ -1481,7 +1481,7 @@ def submit_tool_outputs_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AssistantStreamManager[AssistantEventHandler] | AssistantStreamManager[AssistantEventHandlerT]: """ Submit the tool outputs from a previous run and stream the run to a terminal @@ -1580,7 +1580,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run: """ Create a run. @@ -1731,7 +1731,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncStream[AssistantStreamEvent]: """ Create a run. @@ -1882,7 +1882,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run | AsyncStream[AssistantStreamEvent]: """ Create a run. @@ -2034,7 +2034,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run | AsyncStream[AssistantStreamEvent]: if not thread_id: raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") @@ -2088,7 +2088,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run: """ Retrieves a run. @@ -2131,7 +2131,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run: """ Modifies a run. @@ -2184,7 +2184,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Run, AsyncCursorPage[Run]]: """ Returns a list of runs belonging to a thread. @@ -2250,7 +2250,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run: """ Cancels a run that is `in_progress`. @@ -2309,7 +2309,7 @@ async def create_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> Run: """ A helper to create a run an poll for a terminal state. More information on Run @@ -2378,7 +2378,7 @@ def create_and_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncAssistantStreamManager[AsyncAssistantEventHandler]: """Create a Run stream""" ... @@ -2410,7 +2410,7 @@ def create_and_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncAssistantStreamManager[AsyncAssistantEventHandlerT]: """Create a Run stream""" ... @@ -2441,7 +2441,7 @@ def create_and_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> ( AsyncAssistantStreamManager[AsyncAssistantEventHandler] | AsyncAssistantStreamManager[AsyncAssistantEventHandlerT] @@ -2500,7 +2500,7 @@ async def poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, poll_interval_ms: int | Omit = omit, ) -> Run: """ @@ -2565,7 +2565,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncAssistantStreamManager[AsyncAssistantEventHandler]: """Create a Run stream""" ... @@ -2599,7 +2599,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncAssistantStreamManager[AsyncAssistantEventHandlerT]: """Create a Run stream""" ... @@ -2632,7 +2632,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> ( AsyncAssistantStreamManager[AsyncAssistantEventHandler] | AsyncAssistantStreamManager[AsyncAssistantEventHandlerT] @@ -2699,7 +2699,7 @@ async def submit_tool_outputs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run: """ When a run has the `status: "requires_action"` and `required_action.type` is @@ -2738,7 +2738,7 @@ async def submit_tool_outputs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncStream[AssistantStreamEvent]: """ When a run has the `status: "requires_action"` and `required_action.type` is @@ -2777,7 +2777,7 @@ async def submit_tool_outputs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run | AsyncStream[AssistantStreamEvent]: """ When a run has the `status: "requires_action"` and `required_action.type` is @@ -2817,7 +2817,7 @@ async def submit_tool_outputs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run | AsyncStream[AssistantStreamEvent]: if not thread_id: raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") @@ -2861,7 +2861,7 @@ async def submit_tool_outputs_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> Run: """ A helper to submit a tool output to a run and poll for a terminal run state. @@ -2901,7 +2901,7 @@ def submit_tool_outputs_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncAssistantStreamManager[AsyncAssistantEventHandler]: """ Submit the tool outputs from a previous run and stream the run to a terminal @@ -2924,7 +2924,7 @@ def submit_tool_outputs_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncAssistantStreamManager[AsyncAssistantEventHandlerT]: """ Submit the tool outputs from a previous run and stream the run to a terminal @@ -2946,7 +2946,7 @@ def submit_tool_outputs_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> ( AsyncAssistantStreamManager[AsyncAssistantEventHandler] | AsyncAssistantStreamManager[AsyncAssistantEventHandlerT] diff --git a/src/openai/resources/beta/threads/runs/steps.py b/src/openai/resources/beta/threads/runs/steps.py index 56631966da..784c7f49b8 100644 --- a/src/openai/resources/beta/threads/runs/steps.py +++ b/src/openai/resources/beta/threads/runs/steps.py @@ -6,7 +6,7 @@ from typing import List from typing_extensions import Literal -import httpx +import httpx2 from ..... import _legacy_response from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -58,7 +58,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RunStep: """ Retrieves a run step. @@ -121,7 +121,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[RunStep]: """ Returns a list of run steps belonging to a run. @@ -223,7 +223,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RunStep: """ Retrieves a run step. @@ -286,7 +286,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[RunStep, AsyncCursorPage[RunStep]]: """ Returns a list of run steps belonging to a run. diff --git a/src/openai/resources/beta/threads/threads.py b/src/openai/resources/beta/threads/threads.py index 3932bd8789..8c568b19b4 100644 --- a/src/openai/resources/beta/threads/threads.py +++ b/src/openai/resources/beta/threads/threads.py @@ -7,7 +7,7 @@ from functools import partial from typing_extensions import Literal, overload -import httpx +import httpx2 from .... import _legacy_response from .messages import ( @@ -103,7 +103,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Thread: """ Create a thread. @@ -163,7 +163,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Thread: """ Retrieves a thread. @@ -204,7 +204,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Thread: """ Modifies a thread. @@ -262,7 +262,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ThreadDeleted: """ Delete a thread. @@ -317,7 +317,7 @@ def create_and_run( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run: """ Create a thread and run it in one request. @@ -451,7 +451,7 @@ def create_and_run( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Stream[AssistantStreamEvent]: """ Create a thread and run it in one request. @@ -585,7 +585,7 @@ def create_and_run( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run | Stream[AssistantStreamEvent]: """ Create a thread and run it in one request. @@ -720,7 +720,7 @@ def create_and_run( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run | Stream[AssistantStreamEvent]: extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( @@ -785,7 +785,7 @@ def create_and_run_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> Run: """ A helper to create a thread, start a run and then poll for a terminal state. @@ -840,7 +840,7 @@ def create_and_run_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AssistantStreamManager[AssistantEventHandler]: """Create a thread and stream the run back""" ... @@ -870,7 +870,7 @@ def create_and_run_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AssistantStreamManager[AssistantEventHandlerT]: """Create a thread and stream the run back""" ... @@ -899,7 +899,7 @@ def create_and_run_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AssistantStreamManager[AssistantEventHandler] | AssistantStreamManager[AssistantEventHandlerT]: """Create a thread and stream the run back""" extra_headers = { @@ -990,7 +990,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Thread: """ Create a thread. @@ -1050,7 +1050,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Thread: """ Retrieves a thread. @@ -1091,7 +1091,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Thread: """ Modifies a thread. @@ -1149,7 +1149,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ThreadDeleted: """ Delete a thread. @@ -1204,7 +1204,7 @@ async def create_and_run( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run: """ Create a thread and run it in one request. @@ -1338,7 +1338,7 @@ async def create_and_run( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncStream[AssistantStreamEvent]: """ Create a thread and run it in one request. @@ -1472,7 +1472,7 @@ async def create_and_run( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run | AsyncStream[AssistantStreamEvent]: """ Create a thread and run it in one request. @@ -1607,7 +1607,7 @@ async def create_and_run( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Run | AsyncStream[AssistantStreamEvent]: extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( @@ -1672,7 +1672,7 @@ async def create_and_run_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> Run: """ A helper to create a thread, start a run and then poll for a terminal state. @@ -1729,7 +1729,7 @@ def create_and_run_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncAssistantStreamManager[AsyncAssistantEventHandler]: """Create a thread and stream the run back""" ... @@ -1759,7 +1759,7 @@ def create_and_run_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncAssistantStreamManager[AsyncAssistantEventHandlerT]: """Create a thread and stream the run back""" ... @@ -1788,7 +1788,7 @@ def create_and_run_stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> ( AsyncAssistantStreamManager[AsyncAssistantEventHandler] | AsyncAssistantStreamManager[AsyncAssistantEventHandlerT] diff --git a/src/openai/resources/chat/completions/completions.py b/src/openai/resources/chat/completions/completions.py index f2fda25efb..a2e36b6ad2 100644 --- a/src/openai/resources/chat/completions/completions.py +++ b/src/openai/resources/chat/completions/completions.py @@ -7,7 +7,7 @@ from functools import partial from typing_extensions import Literal, overload -import httpx +import httpx2 import pydantic from .... import _legacy_response @@ -132,7 +132,7 @@ def parse( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ParsedChatCompletion[ResponseFormatT]: """Wrapper over the `client.chat.completions.create()` method that provides richer integrations with Python specific types & returns a `ParsedChatCompletion` object, which is a subclass of the standard `ChatCompletion` class. @@ -294,7 +294,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletion: """ **Starting a new project?** We recommend trying @@ -626,7 +626,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Stream[ChatCompletionChunk]: """ **Starting a new project?** We recommend trying @@ -958,7 +958,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletion | Stream[ChatCompletionChunk]: """ **Starting a new project?** We recommend trying @@ -1290,7 +1290,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletion | Stream[ChatCompletionChunk]: validate_response_format(response_format) return self._post( @@ -1360,7 +1360,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletion: """Get a stored chat completion. @@ -1400,7 +1400,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletion: """Modify a stored chat completion. @@ -1452,7 +1452,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[ChatCompletion]: """List stored Chat Completions. @@ -1514,7 +1514,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletionDeleted: """Delete a stored chat completion. @@ -1588,7 +1588,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletionStreamManager[ResponseFormatT]: """Wrapper over the `client.chat.completions.create(stream=True)` method that provides a more granular event API and automatic accumulation of each delta. @@ -1743,7 +1743,7 @@ async def parse( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ParsedChatCompletion[ResponseFormatT]: """Wrapper over the `client.chat.completions.create()` method that provides richer integrations with Python specific types & returns a `ParsedChatCompletion` object, which is a subclass of the standard `ChatCompletion` class. @@ -1905,7 +1905,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletion: """ **Starting a new project?** We recommend trying @@ -2237,7 +2237,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncStream[ChatCompletionChunk]: """ **Starting a new project?** We recommend trying @@ -2569,7 +2569,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletion | AsyncStream[ChatCompletionChunk]: """ **Starting a new project?** We recommend trying @@ -2901,7 +2901,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletion | AsyncStream[ChatCompletionChunk]: validate_response_format(response_format) return await self._post( @@ -2971,7 +2971,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletion: """Get a stored chat completion. @@ -3011,7 +3011,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletion: """Modify a stored chat completion. @@ -3063,7 +3063,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ChatCompletion, AsyncCursorPage[ChatCompletion]]: """List stored Chat Completions. @@ -3125,7 +3125,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ChatCompletionDeleted: """Delete a stored chat completion. @@ -3199,7 +3199,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncChatCompletionStreamManager[ResponseFormatT]: """Wrapper over the `client.chat.completions.create(stream=True)` method that provides a more granular event API and automatic accumulation of each delta. diff --git a/src/openai/resources/chat/completions/messages.py b/src/openai/resources/chat/completions/messages.py index e5283da4ae..a18c162dae 100644 --- a/src/openai/resources/chat/completions/messages.py +++ b/src/openai/resources/chat/completions/messages.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -56,7 +56,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[ChatCompletionStoreMessage]: """Get the messages in a stored chat completion. @@ -139,7 +139,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ChatCompletionStoreMessage, AsyncCursorPage[ChatCompletionStoreMessage]]: """Get the messages in a stored chat completion. diff --git a/src/openai/resources/completions.py b/src/openai/resources/completions.py index 09d3c63023..160732d9a0 100644 --- a/src/openai/resources/completions.py +++ b/src/openai/resources/completions.py @@ -5,7 +5,7 @@ from typing import Dict, Union, Iterable, Optional from typing_extensions import Literal, overload -import httpx +import httpx2 from .. import _legacy_response from ..types import completion_create_params @@ -75,7 +75,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Completion: """ Creates a completion for the provided prompt and parameters. @@ -233,7 +233,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Stream[Completion]: """ Creates a completion for the provided prompt and parameters. @@ -391,7 +391,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Completion | Stream[Completion]: """ Creates a completion for the provided prompt and parameters. @@ -549,7 +549,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Completion | Stream[Completion]: return self._post( "/completions", @@ -642,7 +642,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Completion: """ Creates a completion for the provided prompt and parameters. @@ -800,7 +800,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncStream[Completion]: """ Creates a completion for the provided prompt and parameters. @@ -958,7 +958,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Completion | AsyncStream[Completion]: """ Creates a completion for the provided prompt and parameters. @@ -1116,7 +1116,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Completion | AsyncStream[Completion]: return await self._post( "/completions", diff --git a/src/openai/resources/containers/containers.py b/src/openai/resources/containers/containers.py index 53da2fc4c0..f0f0c2f506 100644 --- a/src/openai/resources/containers/containers.py +++ b/src/openai/resources/containers/containers.py @@ -5,7 +5,7 @@ from typing import Iterable from typing_extensions import Literal -import httpx +import httpx2 from ... import _legacy_response from ...types import container_list_params, container_create_params @@ -69,7 +69,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ContainerCreateResponse: """ Create Container @@ -127,7 +127,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ContainerRetrieveResponse: """ Retrieve Container @@ -167,7 +167,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[ContainerListResponse]: """List Containers @@ -226,7 +226,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Delete Container @@ -294,7 +294,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ContainerCreateResponse: """ Create Container @@ -352,7 +352,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ContainerRetrieveResponse: """ Retrieve Container @@ -392,7 +392,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ContainerListResponse, AsyncCursorPage[ContainerListResponse]]: """List Containers @@ -451,7 +451,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Delete Container diff --git a/src/openai/resources/containers/files/content.py b/src/openai/resources/containers/files/content.py index 235722ac89..826aab8af5 100644 --- a/src/openai/resources/containers/files/content.py +++ b/src/openai/resources/containers/files/content.py @@ -2,7 +2,7 @@ from __future__ import annotations -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Query, Headers, NotGiven, not_given @@ -50,7 +50,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Retrieve Container File Content @@ -114,7 +114,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Retrieve Container File Content diff --git a/src/openai/resources/containers/files/files.py b/src/openai/resources/containers/files/files.py index 56026a9eab..5511c443ef 100644 --- a/src/openai/resources/containers/files/files.py +++ b/src/openai/resources/containers/files/files.py @@ -5,7 +5,7 @@ from typing import Mapping, cast from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from .content import ( @@ -67,7 +67,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FileCreateResponse: """ Create a Container File @@ -127,7 +127,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FileRetrieveResponse: """ Retrieve Container File @@ -169,7 +169,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[FileListResponse]: """List Container files @@ -228,7 +228,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Delete Container File @@ -295,7 +295,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FileCreateResponse: """ Create a Container File @@ -355,7 +355,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FileRetrieveResponse: """ Retrieve Container File @@ -397,7 +397,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[FileListResponse, AsyncCursorPage[FileListResponse]]: """List Container files @@ -456,7 +456,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Delete Container File diff --git a/src/openai/resources/content_provenance_checks.py b/src/openai/resources/content_provenance_checks.py index 4660972336..dbdc7d5b2f 100644 --- a/src/openai/resources/content_provenance_checks.py +++ b/src/openai/resources/content_provenance_checks.py @@ -4,7 +4,7 @@ from typing import Mapping, cast -import httpx +import httpx2 from .. import _legacy_response from ..types import content_provenance_check_create_params @@ -49,7 +49,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ContentProvenanceCheck: """ Check whether an image or audio file contains known OpenAI provenance signals. @@ -123,7 +123,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ContentProvenanceCheck: """ Check whether an image or audio file contains known OpenAI provenance signals. diff --git a/src/openai/resources/conversations/conversations.py b/src/openai/resources/conversations/conversations.py index f69e73c4c0..bc62041c9e 100644 --- a/src/openai/resources/conversations/conversations.py +++ b/src/openai/resources/conversations/conversations.py @@ -4,7 +4,7 @@ from typing import Iterable, Optional -import httpx +import httpx2 from ... import _legacy_response from .items import ( @@ -67,7 +67,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Conversation: """ Create a conversation. @@ -119,7 +119,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Conversation: """ Get a conversation @@ -157,7 +157,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Conversation: """ Update a conversation @@ -202,7 +202,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ConversationDeletedResource: """Delete a conversation. @@ -269,7 +269,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Conversation: """ Create a conversation. @@ -321,7 +321,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Conversation: """ Get a conversation @@ -359,7 +359,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Conversation: """ Update a conversation @@ -406,7 +406,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ConversationDeletedResource: """Delete a conversation. diff --git a/src/openai/resources/conversations/items.py b/src/openai/resources/conversations/items.py index 339c145ca3..9ae0d2a30b 100644 --- a/src/openai/resources/conversations/items.py +++ b/src/openai/resources/conversations/items.py @@ -5,7 +5,7 @@ from typing import Any, List, Iterable, cast from typing_extensions import Literal -import httpx +import httpx2 from ... import _legacy_response from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -58,7 +58,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ConversationItemList: """ Create items in a conversation with the given ID. @@ -105,7 +105,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ConversationItem: """ Get a single item from a conversation with the given IDs. @@ -158,7 +158,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[ConversationItem]: """ List all items for a conversation with the given ID. @@ -235,7 +235,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Conversation: """ Delete an item from a conversation with the given IDs. @@ -301,7 +301,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ConversationItemList: """ Create items in a conversation with the given ID. @@ -348,7 +348,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ConversationItem: """ Get a single item from a conversation with the given IDs. @@ -401,7 +401,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ConversationItem, AsyncConversationCursorPage[ConversationItem]]: """ List all items for a conversation with the given ID. @@ -478,7 +478,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Conversation: """ Delete an item from a conversation with the given IDs. diff --git a/src/openai/resources/embeddings.py b/src/openai/resources/embeddings.py index 146d5531f6..5b5a018de5 100644 --- a/src/openai/resources/embeddings.py +++ b/src/openai/resources/embeddings.py @@ -7,7 +7,7 @@ from typing import Union, Iterable, cast from typing_extensions import Literal -import httpx +import httpx2 from .. import _legacy_response from ..types import embedding_create_params @@ -61,7 +61,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> CreateEmbeddingResponse: """ Creates an embedding vector representing the input text. @@ -185,7 +185,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> CreateEmbeddingResponse: """ Creates an embedding vector representing the input text. diff --git a/src/openai/resources/evals/evals.py b/src/openai/resources/evals/evals.py index c7843d7aff..9ebed4a1b6 100644 --- a/src/openai/resources/evals/evals.py +++ b/src/openai/resources/evals/evals.py @@ -5,7 +5,7 @@ from typing import Iterable, Optional from typing_extensions import Literal -import httpx +import httpx2 from ... import _legacy_response from ...types import eval_list_params, eval_create_params, eval_update_params @@ -73,7 +73,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> EvalCreateResponse: """ Create the structure of an evaluation that can be used to test a model's @@ -139,7 +139,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> EvalRetrieveResponse: """ Get an evaluation by ID. @@ -178,7 +178,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> EvalUpdateResponse: """ Update certain properties of an evaluation. @@ -234,7 +234,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[EvalListResponse]: """ List evaluations for a project. @@ -289,7 +289,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> EvalDeleteResponse: """ Delete an evaluation. @@ -357,7 +357,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> EvalCreateResponse: """ Create the structure of an evaluation that can be used to test a model's @@ -423,7 +423,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> EvalRetrieveResponse: """ Get an evaluation by ID. @@ -462,7 +462,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> EvalUpdateResponse: """ Update certain properties of an evaluation. @@ -518,7 +518,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[EvalListResponse, AsyncCursorPage[EvalListResponse]]: """ List evaluations for a project. @@ -573,7 +573,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> EvalDeleteResponse: """ Delete an evaluation. diff --git a/src/openai/resources/evals/runs/output_items.py b/src/openai/resources/evals/runs/output_items.py index aac2c11e55..4a26ccf26c 100644 --- a/src/openai/resources/evals/runs/output_items.py +++ b/src/openai/resources/evals/runs/output_items.py @@ -4,7 +4,7 @@ from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -54,7 +54,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OutputItemRetrieveResponse: """ Get an evaluation run output item by ID. @@ -105,7 +105,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[OutputItemListResponse]: """ Get a list of output items for an evaluation run. @@ -189,7 +189,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> OutputItemRetrieveResponse: """ Get an evaluation run output item by ID. @@ -240,7 +240,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[OutputItemListResponse, AsyncCursorPage[OutputItemListResponse]]: """ Get a list of output items for an evaluation run. diff --git a/src/openai/resources/evals/runs/runs.py b/src/openai/resources/evals/runs/runs.py index c848df91d4..f57db15977 100644 --- a/src/openai/resources/evals/runs/runs.py +++ b/src/openai/resources/evals/runs/runs.py @@ -5,7 +5,7 @@ from typing import Optional from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -73,7 +73,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RunCreateResponse: """ Kicks off a new run for a given evaluation, specifying the data source, and what @@ -132,7 +132,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RunRetrieveResponse: """ Get an evaluation run by ID. @@ -175,7 +175,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[RunListResponse]: """ Get a list of runs for an evaluation. @@ -233,7 +233,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RunDeleteResponse: """ Delete an eval run. @@ -273,7 +273,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RunCancelResponse: """ Cancel an ongoing evaluation run. @@ -343,7 +343,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RunCreateResponse: """ Kicks off a new run for a given evaluation, specifying the data source, and what @@ -402,7 +402,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RunRetrieveResponse: """ Get an evaluation run by ID. @@ -445,7 +445,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[RunListResponse, AsyncCursorPage[RunListResponse]]: """ Get a list of runs for an evaluation. @@ -503,7 +503,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RunDeleteResponse: """ Delete an eval run. @@ -543,7 +543,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> RunCancelResponse: """ Cancel an ongoing evaluation run. diff --git a/src/openai/resources/files.py b/src/openai/resources/files.py index 74b4d1c06a..19db280256 100644 --- a/src/openai/resources/files.py +++ b/src/openai/resources/files.py @@ -7,7 +7,7 @@ from typing import Mapping, cast from typing_extensions import Literal -import httpx +import httpx2 from .. import _legacy_response from ..types import FilePurpose, file_list_params, file_create_params @@ -68,7 +68,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FileObject: """Upload a file that can be used across various endpoints. @@ -160,7 +160,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FileObject: """ Returns information about a specific file. @@ -200,7 +200,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[FileObject]: """Returns a list of files. @@ -259,7 +259,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FileDeleted: """ Delete a file and remove it from all vector stores. @@ -296,7 +296,7 @@ def content( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Returns the contents of the specified file. @@ -335,7 +335,7 @@ def retrieve_content( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> str: """ Returns the contents of the specified file. @@ -422,7 +422,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FileObject: """Upload a file that can be used across various endpoints. @@ -514,7 +514,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FileObject: """ Returns information about a specific file. @@ -554,7 +554,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[FileObject, AsyncCursorPage[FileObject]]: """Returns a list of files. @@ -613,7 +613,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FileDeleted: """ Delete a file and remove it from all vector stores. @@ -650,7 +650,7 @@ async def content( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Returns the contents of the specified file. @@ -689,7 +689,7 @@ async def retrieve_content( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> str: """ Returns the contents of the specified file. diff --git a/src/openai/resources/fine_tuning/alpha/graders.py b/src/openai/resources/fine_tuning/alpha/graders.py index dd31605843..d7fa11ec50 100644 --- a/src/openai/resources/fine_tuning/alpha/graders.py +++ b/src/openai/resources/fine_tuning/alpha/graders.py @@ -2,7 +2,7 @@ from __future__ import annotations -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -51,7 +51,7 @@ def run( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> GraderRunResponse: """ Run a grader. @@ -106,7 +106,7 @@ def validate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> GraderValidateResponse: """ Validate a grader. @@ -169,7 +169,7 @@ async def run( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> GraderRunResponse: """ Run a grader. @@ -224,7 +224,7 @@ async def validate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> GraderValidateResponse: """ Validate a grader. diff --git a/src/openai/resources/fine_tuning/checkpoints/permissions.py b/src/openai/resources/fine_tuning/checkpoints/permissions.py index 8337362ba8..7ac669da31 100644 --- a/src/openai/resources/fine_tuning/checkpoints/permissions.py +++ b/src/openai/resources/fine_tuning/checkpoints/permissions.py @@ -5,7 +5,7 @@ import typing_extensions from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given @@ -60,7 +60,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncPage[PermissionCreateResponse]: """ **NOTE:** Calling this endpoint requires an [admin API key](../admin-api-keys). @@ -115,7 +115,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> PermissionRetrieveResponse: """ **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). @@ -181,7 +181,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[PermissionListResponse]: """ **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). @@ -245,7 +245,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> PermissionDeleteResponse: """ **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). @@ -317,7 +317,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[PermissionCreateResponse, AsyncPage[PermissionCreateResponse]]: """ **NOTE:** Calling this endpoint requires an [admin API key](../admin-api-keys). @@ -372,7 +372,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> PermissionRetrieveResponse: """ **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). @@ -438,7 +438,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[PermissionListResponse, AsyncConversationCursorPage[PermissionListResponse]]: """ **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). @@ -502,7 +502,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> PermissionDeleteResponse: """ **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). diff --git a/src/openai/resources/fine_tuning/jobs/checkpoints.py b/src/openai/resources/fine_tuning/jobs/checkpoints.py index 49dd593287..2476b66014 100644 --- a/src/openai/resources/fine_tuning/jobs/checkpoints.py +++ b/src/openai/resources/fine_tuning/jobs/checkpoints.py @@ -2,7 +2,7 @@ from __future__ import annotations -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -54,7 +54,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[FineTuningJobCheckpoint]: """ List checkpoints for a fine-tuning job. @@ -128,7 +128,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[FineTuningJobCheckpoint, AsyncCursorPage[FineTuningJobCheckpoint]]: """ List checkpoints for a fine-tuning job. diff --git a/src/openai/resources/fine_tuning/jobs/jobs.py b/src/openai/resources/fine_tuning/jobs/jobs.py index ef6e54bed0..a6046cdd0f 100644 --- a/src/openai/resources/fine_tuning/jobs/jobs.py +++ b/src/openai/resources/fine_tuning/jobs/jobs.py @@ -5,7 +5,7 @@ from typing import Dict, Union, Iterable, Optional from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -78,7 +78,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Creates a fine-tuning job which begins the process of creating a new model from @@ -193,7 +193,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Get info about a fine-tuning job. @@ -234,7 +234,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[FineTuningJob]: """ List your organization's fine-tuning jobs @@ -285,7 +285,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Immediately cancel a fine-tune job. @@ -324,7 +324,7 @@ def list_events( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[FineTuningJobEvent]: """ Get status updates for a fine-tuning job. @@ -373,7 +373,7 @@ def pause( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Pause a fine-tune job. @@ -410,7 +410,7 @@ def resume( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Resume a fine-tune job. @@ -483,7 +483,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Creates a fine-tuning job which begins the process of creating a new model from @@ -598,7 +598,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Get info about a fine-tuning job. @@ -639,7 +639,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[FineTuningJob, AsyncCursorPage[FineTuningJob]]: """ List your organization's fine-tuning jobs @@ -690,7 +690,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Immediately cancel a fine-tune job. @@ -729,7 +729,7 @@ def list_events( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[FineTuningJobEvent, AsyncCursorPage[FineTuningJobEvent]]: """ Get status updates for a fine-tuning job. @@ -778,7 +778,7 @@ async def pause( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Pause a fine-tune job. @@ -815,7 +815,7 @@ async def resume( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Resume a fine-tune job. diff --git a/src/openai/resources/images.py b/src/openai/resources/images.py index d4bcbcafba..e0aad166fc 100644 --- a/src/openai/resources/images.py +++ b/src/openai/resources/images.py @@ -5,7 +5,7 @@ from typing import Union, Mapping, Optional, cast from typing_extensions import Literal, overload -import httpx +import httpx2 from .. import _legacy_response from ..types import image_edit_params, image_generate_params, image_create_variation_params @@ -61,7 +61,7 @@ def create_variation( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse: """Creates a variation of a given image. @@ -150,7 +150,7 @@ def edit( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse: """Creates an edited or extended image given one or more source images and a prompt. @@ -280,7 +280,7 @@ def edit( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Stream[ImageEditStreamEvent]: """Creates an edited or extended image given one or more source images and a prompt. @@ -410,7 +410,7 @@ def edit( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse | Stream[ImageEditStreamEvent]: """Creates an edited or extended image given one or more source images and a prompt. @@ -540,7 +540,7 @@ def edit( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse | Stream[ImageEditStreamEvent]: body = deepcopy_with_paths( { @@ -614,7 +614,7 @@ def generate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse: """ Creates an image given a prompt. @@ -740,7 +740,7 @@ def generate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Stream[ImageGenStreamEvent]: """ Creates an image given a prompt. @@ -866,7 +866,7 @@ def generate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse | Stream[ImageGenStreamEvent]: """ Creates an image given a prompt. @@ -992,7 +992,7 @@ def generate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse | Stream[ImageGenStreamEvent]: return self._post( "/images/generations", @@ -1066,7 +1066,7 @@ async def create_variation( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse: """Creates a variation of a given image. @@ -1155,7 +1155,7 @@ async def edit( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse: """Creates an edited or extended image given one or more source images and a prompt. @@ -1285,7 +1285,7 @@ async def edit( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncStream[ImageEditStreamEvent]: """Creates an edited or extended image given one or more source images and a prompt. @@ -1415,7 +1415,7 @@ async def edit( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse | AsyncStream[ImageEditStreamEvent]: """Creates an edited or extended image given one or more source images and a prompt. @@ -1545,7 +1545,7 @@ async def edit( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse | AsyncStream[ImageEditStreamEvent]: body = deepcopy_with_paths( { @@ -1619,7 +1619,7 @@ async def generate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse: """ Creates an image given a prompt. @@ -1745,7 +1745,7 @@ async def generate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncStream[ImageGenStreamEvent]: """ Creates an image given a prompt. @@ -1871,7 +1871,7 @@ async def generate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse | AsyncStream[ImageGenStreamEvent]: """ Creates an image given a prompt. @@ -1997,7 +1997,7 @@ async def generate( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ImagesResponse | AsyncStream[ImageGenStreamEvent]: return await self._post( "/images/generations", diff --git a/src/openai/resources/models.py b/src/openai/resources/models.py index acd55efbed..41152fb91f 100644 --- a/src/openai/resources/models.py +++ b/src/openai/resources/models.py @@ -2,7 +2,7 @@ from __future__ import annotations -import httpx +import httpx2 from .. import _legacy_response from .._types import Body, Query, Headers, NotGiven, not_given @@ -52,7 +52,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Model: """ Retrieves a model instance, providing basic information about the model such as @@ -89,7 +89,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncPage[Model]: """ Lists the currently available models, and provides basic information about each @@ -117,7 +117,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ModelDeleted: """Delete a fine-tuned model. @@ -179,7 +179,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Model: """ Retrieves a model instance, providing basic information about the model such as @@ -216,7 +216,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Model, AsyncPage[Model]]: """ Lists the currently available models, and provides basic information about each @@ -244,7 +244,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ModelDeleted: """Delete a fine-tuned model. diff --git a/src/openai/resources/moderations.py b/src/openai/resources/moderations.py index 35eec47206..b29de575f2 100644 --- a/src/openai/resources/moderations.py +++ b/src/openai/resources/moderations.py @@ -4,7 +4,7 @@ from typing import Union, Iterable -import httpx +import httpx2 from .. import _legacy_response from ..types import moderation_create_params @@ -55,7 +55,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ModerationCreateResponse: """Classifies if text and/or image inputs are potentially harmful. @@ -133,7 +133,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ModerationCreateResponse: """Classifies if text and/or image inputs are potentially harmful. diff --git a/src/openai/resources/realtime/calls.py b/src/openai/resources/realtime/calls.py index d622dce409..3bc6817f5d 100644 --- a/src/openai/resources/realtime/calls.py +++ b/src/openai/resources/realtime/calls.py @@ -5,7 +5,7 @@ from typing import List, Union, Optional from typing_extensions import Literal -import httpx +import httpx2 from ... import _legacy_response from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given @@ -69,7 +69,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Create a new Realtime API call over WebRTC and receive the SDP answer needed to @@ -155,7 +155,7 @@ def accept( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Accept an incoming SIP call and configure the realtime session that will handle @@ -285,7 +285,7 @@ def hangup( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ End an active Realtime API call, whether it was initiated over SIP or WebRTC. @@ -324,7 +324,7 @@ def refer( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Transfer an active SIP call to a new destination using the SIP REFER verb. @@ -367,7 +367,7 @@ def reject( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Decline an incoming SIP call by returning a SIP status code to the caller. @@ -431,7 +431,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Create a new Realtime API call over WebRTC and receive the SDP answer needed to @@ -517,7 +517,7 @@ async def accept( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Accept an incoming SIP call and configure the realtime session that will handle @@ -647,7 +647,7 @@ async def hangup( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ End an active Realtime API call, whether it was initiated over SIP or WebRTC. @@ -686,7 +686,7 @@ async def refer( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Transfer an active SIP call to a new destination using the SIP REFER verb. @@ -729,7 +729,7 @@ async def reject( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Decline an incoming SIP call by returning a SIP status code to the caller. diff --git a/src/openai/resources/realtime/client_secrets.py b/src/openai/resources/realtime/client_secrets.py index f947712f2d..77c2753cdd 100644 --- a/src/openai/resources/realtime/client_secrets.py +++ b/src/openai/resources/realtime/client_secrets.py @@ -2,7 +2,7 @@ from __future__ import annotations -import httpx +import httpx2 from ... import _legacy_response from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -47,7 +47,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ClientSecretCreateResponse: """ Create a Realtime client secret with an associated session configuration. @@ -133,7 +133,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> ClientSecretCreateResponse: """ Create a Realtime client secret with an associated session configuration. diff --git a/src/openai/resources/realtime/realtime.py b/src/openai/resources/realtime/realtime.py index 8989d9448d..8e42e16870 100644 --- a/src/openai/resources/realtime/realtime.py +++ b/src/openai/resources/realtime/realtime.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any, Union, Callable, Iterator, Awaitable, cast from typing_extensions import AsyncIterator -import httpx +import httpx2 from pydantic import BaseModel from .calls import ( @@ -720,7 +720,7 @@ async def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> Async **self.__websocket_connection_options, ) - def _prepare_url(self) -> httpx.URL: + def _prepare_url(self) -> httpx2.URL: if self.__client.websocket_base_url is not None: base_url = normalize_httpx_url(self.__client.websocket_base_url) else: @@ -1188,7 +1188,7 @@ def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> WebSocketCo **self.__websocket_connection_options, ) - def _prepare_url(self) -> httpx.URL: + def _prepare_url(self) -> httpx2.URL: if self.__client.websocket_base_url is not None: base_url = normalize_httpx_url(self.__client.websocket_base_url) else: diff --git a/src/openai/resources/responses/input_items.py b/src/openai/resources/responses/input_items.py index 9aaf553f22..7be6d24bd7 100644 --- a/src/openai/resources/responses/input_items.py +++ b/src/openai/resources/responses/input_items.py @@ -5,7 +5,7 @@ from typing import Any, List, cast from typing_extensions import Literal -import httpx +import httpx2 from ... import _legacy_response from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -55,7 +55,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[ResponseItem]: """ Returns a list of input items for a given response. @@ -140,7 +140,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ResponseItem, AsyncCursorPage[ResponseItem]]: """ Returns a list of input items for a given response. diff --git a/src/openai/resources/responses/input_tokens.py b/src/openai/resources/responses/input_tokens.py index 1d313d1190..d9f0cca458 100644 --- a/src/openai/resources/responses/input_tokens.py +++ b/src/openai/resources/responses/input_tokens.py @@ -5,7 +5,7 @@ from typing import Union, Iterable, Optional from typing_extensions import Literal -import httpx +import httpx2 from ... import _legacy_response from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -63,7 +63,7 @@ def count( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> InputTokenCountResponse: """ Returns input token counts of the request. @@ -199,7 +199,7 @@ async def count( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> InputTokenCountResponse: """ Returns input token counts of the request. diff --git a/src/openai/resources/responses/responses.py b/src/openai/resources/responses/responses.py index 43c84769b0..bbb4a7a443 100644 --- a/src/openai/resources/responses/responses.py +++ b/src/openai/resources/responses/responses.py @@ -25,7 +25,7 @@ from functools import partial from typing_extensions import Literal, overload -import httpx +import httpx2 from pydantic import BaseModel from ... import _legacy_response @@ -169,7 +169,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response: """Creates a model response. @@ -450,7 +450,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Stream[ResponseStreamEvent]: """Creates a model response. @@ -731,7 +731,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response | Stream[ResponseStreamEvent]: """Creates a model response. @@ -1011,7 +1011,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response | Stream[ResponseStreamEvent]: return self._post( "/responses", @@ -1077,7 +1077,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> ResponseStreamManager[TextFormatT]: ... @overload @@ -1120,7 +1120,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> ResponseStreamManager[TextFormatT]: ... def stream( @@ -1164,7 +1164,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> ResponseStreamManager[TextFormatT]: new_response_args = { "input": input, @@ -1328,7 +1328,7 @@ def parse( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> ParsedResponse[TextFormatT]: if is_given(text_format): if not text: @@ -1414,7 +1414,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response: ... @overload @@ -1430,7 +1430,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> Stream[ResponseStreamEvent]: ... @overload @@ -1446,7 +1446,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> Response | Stream[ResponseStreamEvent]: ... @overload @@ -1462,7 +1462,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> Response | Stream[ResponseStreamEvent]: """ Retrieves a model response with the given ID. @@ -1511,7 +1511,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Stream[ResponseStreamEvent]: """ Retrieves a model response with the given ID. @@ -1560,7 +1560,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response | Stream[ResponseStreamEvent]: """ Retrieves a model response with the given ID. @@ -1608,7 +1608,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response | Stream[ResponseStreamEvent]: if not response_id: raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}") @@ -1644,7 +1644,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Deletes a model response with the given ID. @@ -1682,7 +1682,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response: """Cancels a model response with the given ID. @@ -1833,7 +1833,7 @@ def compact( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> CompactedResponse: """Compact a conversation. @@ -2022,7 +2022,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response: """Creates a model response. @@ -2303,7 +2303,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncStream[ResponseStreamEvent]: """Creates a model response. @@ -2584,7 +2584,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response | AsyncStream[ResponseStreamEvent]: """Creates a model response. @@ -2864,7 +2864,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response | AsyncStream[ResponseStreamEvent]: return await self._post( "/responses", @@ -2930,7 +2930,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncResponseStreamManager[TextFormatT]: ... @overload @@ -2973,7 +2973,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncResponseStreamManager[TextFormatT]: ... def stream( @@ -3017,7 +3017,7 @@ def stream( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncResponseStreamManager[TextFormatT]: new_response_args = { "input": input, @@ -3180,7 +3180,7 @@ async def parse( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> ParsedResponse[TextFormatT]: if is_given(text_format): if not text: @@ -3266,7 +3266,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response: ... @overload @@ -3282,7 +3282,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncStream[ResponseStreamEvent]: ... @overload @@ -3298,7 +3298,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> Response | AsyncStream[ResponseStreamEvent]: ... @overload @@ -3314,7 +3314,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx2.Timeout | None | NotGiven = NOT_GIVEN, ) -> Response | AsyncStream[ResponseStreamEvent]: """ Retrieves a model response with the given ID. @@ -3363,7 +3363,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncStream[ResponseStreamEvent]: """ Retrieves a model response with the given ID. @@ -3412,7 +3412,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response | AsyncStream[ResponseStreamEvent]: """ Retrieves a model response with the given ID. @@ -3460,7 +3460,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response | AsyncStream[ResponseStreamEvent]: if not response_id: raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}") @@ -3496,7 +3496,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> None: """ Deletes a model response with the given ID. @@ -3534,7 +3534,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Response: """Cancels a model response with the given ID. @@ -3685,7 +3685,7 @@ async def compact( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> CompactedResponse: """Compact a conversation. @@ -4405,7 +4405,7 @@ async def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> Async **self.__websocket_connection_options, ) - def _prepare_url(self) -> httpx.URL: + def _prepare_url(self) -> httpx2.URL: if self.__client.websocket_base_url is not None: base_url = normalize_httpx_url(self.__client.websocket_base_url) else: @@ -4850,7 +4850,7 @@ def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> WebSocketCo **self.__websocket_connection_options, ) - def _prepare_url(self) -> httpx.URL: + def _prepare_url(self) -> httpx2.URL: if self.__client.websocket_base_url is not None: base_url = normalize_httpx_url(self.__client.websocket_base_url) else: diff --git a/src/openai/resources/skills/content.py b/src/openai/resources/skills/content.py index eedbb90b23..91ac17d39f 100644 --- a/src/openai/resources/skills/content.py +++ b/src/openai/resources/skills/content.py @@ -2,7 +2,7 @@ from __future__ import annotations -import httpx +import httpx2 from ... import _legacy_response from ..._types import Body, Query, Headers, NotGiven, not_given @@ -49,7 +49,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Download a skill zip bundle by its ID. @@ -108,7 +108,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Download a skill zip bundle by its ID. diff --git a/src/openai/resources/skills/skills.py b/src/openai/resources/skills/skills.py index 0ad1b42587..7c840a901c 100644 --- a/src/openai/resources/skills/skills.py +++ b/src/openai/resources/skills/skills.py @@ -5,7 +5,7 @@ from typing import Union, Mapping, cast from typing_extensions import Literal -import httpx +import httpx2 from ... import _legacy_response from ...types import skill_list_params, skill_create_params, skill_update_params @@ -86,7 +86,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Skill: """ Create a new skill. @@ -132,7 +132,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Skill: """ Get a skill by its ID. @@ -170,7 +170,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Skill: """ Update the default version pointer for a skill. @@ -212,7 +212,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Skill]: """ List all skills for the current project. @@ -263,7 +263,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> DeletedSkill: """ Delete a skill by its ID. @@ -329,7 +329,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Skill: """ Create a new skill. @@ -375,7 +375,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Skill: """ Get a skill by its ID. @@ -413,7 +413,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Skill: """ Update the default version pointer for a skill. @@ -457,7 +457,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Skill, AsyncCursorPage[Skill]]: """ List all skills for the current project. @@ -508,7 +508,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> DeletedSkill: """ Delete a skill by its ID. diff --git a/src/openai/resources/skills/versions/content.py b/src/openai/resources/skills/versions/content.py index c72c8c4f08..241b3f6e54 100644 --- a/src/openai/resources/skills/versions/content.py +++ b/src/openai/resources/skills/versions/content.py @@ -2,7 +2,7 @@ from __future__ import annotations -import httpx +import httpx2 from .... import _legacy_response from ...._types import Body, Query, Headers, NotGiven, not_given @@ -50,7 +50,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Download a skill version zip bundle. @@ -114,7 +114,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Download a skill version zip bundle. diff --git a/src/openai/resources/skills/versions/versions.py b/src/openai/resources/skills/versions/versions.py index 17f85a13ec..b4efd79dfa 100644 --- a/src/openai/resources/skills/versions/versions.py +++ b/src/openai/resources/skills/versions/versions.py @@ -5,7 +5,7 @@ from typing import Union, Mapping, cast from typing_extensions import Literal -import httpx +import httpx2 from .... import _legacy_response from .content import ( @@ -76,7 +76,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SkillVersion: """ Create a new immutable skill version. @@ -133,7 +133,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SkillVersion: """ Get a specific skill version. @@ -177,7 +177,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[SkillVersion]: """ List skill versions for a skill. @@ -230,7 +230,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> DeletedSkillVersion: """ Delete a skill version. @@ -298,7 +298,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SkillVersion: """ Create a new immutable skill version. @@ -355,7 +355,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SkillVersion: """ Get a specific skill version. @@ -399,7 +399,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[SkillVersion, AsyncCursorPage[SkillVersion]]: """ List skill versions for a skill. @@ -452,7 +452,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> DeletedSkillVersion: """ Delete a skill version. diff --git a/src/openai/resources/uploads/parts.py b/src/openai/resources/uploads/parts.py index d04b037b44..f9c88c1cc5 100644 --- a/src/openai/resources/uploads/parts.py +++ b/src/openai/resources/uploads/parts.py @@ -4,7 +4,7 @@ from typing import Mapping, cast -import httpx +import httpx2 from ... import _legacy_response from ..._files import deepcopy_with_paths @@ -52,7 +52,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UploadPart: """ Adds a @@ -133,7 +133,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> UploadPart: """ Adds a diff --git a/src/openai/resources/uploads/uploads.py b/src/openai/resources/uploads/uploads.py index fa6b580df9..1ab8d8b8c6 100644 --- a/src/openai/resources/uploads/uploads.py +++ b/src/openai/resources/uploads/uploads.py @@ -10,7 +10,7 @@ from pathlib import Path import anyio -import httpx +import httpx2 from ... import _legacy_response from .parts import ( @@ -178,7 +178,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Upload: """ Creates an intermediate @@ -260,7 +260,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Upload: """Cancels the Upload. @@ -302,7 +302,7 @@ def complete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Upload: """ Completes the @@ -506,7 +506,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Upload: """ Creates an intermediate @@ -588,7 +588,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Upload: """Cancels the Upload. @@ -630,7 +630,7 @@ async def complete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Upload: """ Completes the diff --git a/src/openai/resources/vector_stores/file_batches.py b/src/openai/resources/vector_stores/file_batches.py index 6e48c2d07b..9bd5040172 100644 --- a/src/openai/resources/vector_stores/file_batches.py +++ b/src/openai/resources/vector_stores/file_batches.py @@ -7,7 +7,7 @@ from typing_extensions import Union, Literal from concurrent.futures import Future, ThreadPoolExecutor, as_completed -import httpx +import httpx2 import sniffio from ... import _legacy_response @@ -61,7 +61,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """ Create a vector store file batch. @@ -132,7 +132,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """ Retrieves a vector store file batch. @@ -177,7 +177,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """Cancel a vector store file batch. @@ -228,7 +228,7 @@ def create_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """Create a vector store batch and poll until all files have been processed.""" batch = self.create( @@ -264,7 +264,7 @@ def list_files( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[VectorStoreFile]: """ Returns a list of vector store files in a batch. @@ -449,7 +449,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """ Create a vector store file batch. @@ -520,7 +520,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """ Retrieves a vector store file batch. @@ -565,7 +565,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """Cancel a vector store file batch. @@ -616,7 +616,7 @@ async def create_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """Create a vector store batch and poll until all files have been processed.""" batch = await self.create( @@ -652,7 +652,7 @@ def list_files( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[VectorStoreFile, AsyncCursorPage[VectorStoreFile]]: """ Returns a list of vector store files in a batch. diff --git a/src/openai/resources/vector_stores/files.py b/src/openai/resources/vector_stores/files.py index 69534b0c99..06ae07129d 100644 --- a/src/openai/resources/vector_stores/files.py +++ b/src/openai/resources/vector_stores/files.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Dict, Union, Optional from typing_extensions import Literal, assert_never -import httpx +import httpx2 from ... import _legacy_response from ...types import FileChunkingStrategyParam @@ -57,7 +57,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """ Create a vector store file by attaching a @@ -121,7 +121,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """ Retrieves a vector store file. @@ -165,7 +165,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """ Update attributes on a vector store file. @@ -219,7 +219,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[VectorStoreFile]: """ Returns a list of vector store files. @@ -287,7 +287,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileDeleted: """Delete a vector store file. @@ -337,7 +337,7 @@ def create_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """Attach a file to the given vector store and wait for it to be processed.""" self.create( @@ -442,7 +442,7 @@ def content( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncPage[FileContentResponse]: """ Retrieve the parsed contents of a vector store file. @@ -511,7 +511,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """ Create a vector store file by attaching a @@ -575,7 +575,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """ Retrieves a vector store file. @@ -619,7 +619,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """ Update attributes on a vector store file. @@ -673,7 +673,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[VectorStoreFile, AsyncCursorPage[VectorStoreFile]]: """ Returns a list of vector store files. @@ -741,7 +741,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileDeleted: """Delete a vector store file. @@ -791,7 +791,7 @@ async def create_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """Attach a file to the given vector store and wait for it to be processed.""" await self.create( @@ -898,7 +898,7 @@ def content( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[FileContentResponse, AsyncPage[FileContentResponse]]: """ Retrieve the parsed contents of a vector store file. diff --git a/src/openai/resources/vector_stores/vector_stores.py b/src/openai/resources/vector_stores/vector_stores.py index 826f808e5b..a177c368c1 100644 --- a/src/openai/resources/vector_stores/vector_stores.py +++ b/src/openai/resources/vector_stores/vector_stores.py @@ -5,7 +5,7 @@ from typing import Union, Optional from typing_extensions import Literal -import httpx +import httpx2 from ... import _legacy_response from .files import ( @@ -89,7 +89,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStore: """ Create a vector store. @@ -157,7 +157,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStore: """ Retrieves a vector store. @@ -198,7 +198,7 @@ def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStore: """ Modifies a vector store. @@ -258,7 +258,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[VectorStore]: """Returns a list of vector stores. @@ -321,7 +321,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreDeleted: """ Delete a vector store. @@ -364,7 +364,7 @@ def search( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncPage[VectorStoreSearchResponse]: """ Search a vector store for relevant chunks based on a query and file attributes @@ -460,7 +460,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStore: """ Create a vector store. @@ -528,7 +528,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStore: """ Retrieves a vector store. @@ -569,7 +569,7 @@ async def update( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStore: """ Modifies a vector store. @@ -629,7 +629,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[VectorStore, AsyncCursorPage[VectorStore]]: """Returns a list of vector stores. @@ -692,7 +692,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreDeleted: """ Delete a vector store. @@ -735,7 +735,7 @@ def search( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[VectorStoreSearchResponse, AsyncPage[VectorStoreSearchResponse]]: """ Search a vector store for relevant chunks based on a query and file attributes diff --git a/src/openai/resources/videos.py b/src/openai/resources/videos.py index 090ab62fa9..37ceb9a660 100644 --- a/src/openai/resources/videos.py +++ b/src/openai/resources/videos.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Mapping, cast from typing_extensions import Literal, assert_never -import httpx +import httpx2 from .. import _legacy_response from ..types import ( @@ -79,7 +79,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Video: """ Create a new video generation job from a prompt and optional reference assets. @@ -148,7 +148,7 @@ def create_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Video: """Create a video and wait for it to be processed.""" video = self.create( @@ -216,7 +216,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Video: """ Fetch the latest metadata for a generated video. @@ -255,7 +255,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> SyncConversationCursorPage[Video]: """ List recently generated videos for the current project. @@ -306,7 +306,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VideoDeleteResponse: """ Permanently delete a completed or failed video and its stored assets. @@ -344,7 +344,7 @@ def create_character( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VideoCreateCharacterResponse: """ Create a character from an uploaded video. @@ -398,7 +398,7 @@ def download_content( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Download the generated video bytes or a derived preview asset. @@ -442,7 +442,7 @@ def edit( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Video: """ Create a new video generation job by editing a source video or existing @@ -498,7 +498,7 @@ def extend( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Video: """ Create an extension of a completed video. @@ -555,7 +555,7 @@ def get_character( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VideoGetCharacterResponse: """ Fetch a character. @@ -593,7 +593,7 @@ def remix( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Video: """ Create a remix of a completed video using a refreshed prompt. @@ -658,7 +658,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Video: """ Create a new video generation job from a prompt and optional reference assets. @@ -727,7 +727,7 @@ async def create_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Video: """Create a video and wait for it to be processed.""" video = await self.create( @@ -795,7 +795,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Video: """ Fetch the latest metadata for a generated video. @@ -834,7 +834,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Video, AsyncConversationCursorPage[Video]]: """ List recently generated videos for the current project. @@ -885,7 +885,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VideoDeleteResponse: """ Permanently delete a completed or failed video and its stored assets. @@ -923,7 +923,7 @@ async def create_character( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VideoCreateCharacterResponse: """ Create a character from an uploaded video. @@ -977,7 +977,7 @@ async def download_content( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Download the generated video bytes or a derived preview asset. @@ -1023,7 +1023,7 @@ async def edit( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Video: """ Create a new video generation job by editing a source video or existing @@ -1079,7 +1079,7 @@ async def extend( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Video: """ Create an extension of a completed video. @@ -1136,7 +1136,7 @@ async def get_character( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VideoGetCharacterResponse: """ Fetch a character. @@ -1174,7 +1174,7 @@ async def remix( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> Video: """ Create a remix of a completed video using a refreshed prompt. diff --git a/tests/_httpx2_respx.py b/tests/_httpx2_respx.py deleted file mode 100644 index 3b66323daf..0000000000 --- a/tests/_httpx2_respx.py +++ /dev/null @@ -1,92 +0,0 @@ -from __future__ import annotations - -import os -from typing import Any, NoReturn - -import httpx -import pytest -from respx import MockRouter - -from openai import DefaultHttpx2Client, DefaultAsyncHttpx2Client - - -def httpx2_enabled() -> bool: - return os.environ.get("OPENAI_TEST_HTTP_CLIENT") == "httpx2" - - -def sync_http_client(**kwargs: Any) -> httpx.Client: - if httpx2_enabled(): - return DefaultHttpx2Client(**kwargs) - return httpx.Client(**kwargs) - - -def async_http_client(**kwargs: Any) -> httpx.AsyncClient: - if httpx2_enabled(): - return DefaultAsyncHttpx2Client(**kwargs) - return httpx.AsyncClient(**kwargs) - - -def enable_httpx2_respx( - router: MockRouter, monkeypatch: pytest.MonkeyPatch, *, replace_sdk_defaults: bool = True -) -> None: - httpx2 = pytest.importorskip("httpx2") - - # RESPX deliberately only understands HTTPX objects. Keep the SDK side native and - # translate at this single test-only boundary so existing routes, callbacks, and - # call assertions can be exercised against HTTPX2 without copied test cases. - def httpx_request(native_request: Any, *, content: bytes) -> httpx.Request: - return httpx.Request( - native_request.method, - str(native_request.url), - headers=list(native_request.headers.multi_items()), - content=content, - extensions=dict(native_request.extensions), - ) - - def httpx2_response(response: httpx.Response, *, native_request: Any, content: bytes) -> Any: - return httpx2.Response( - response.status_code, - headers=list(response.headers.multi_items()), - # Supplying a stream keeps streaming-response tests meaningful: the native - # response stays open until the SDK consumes or closes it. - stream=httpx2.ByteStream(content), - request=native_request, - extensions=dict(response.extensions), - ) - - def raise_native_request_error(exc: httpx.RequestError, native_request: Any) -> NoReturn: - native_error = getattr(httpx2, type(exc).__name__, httpx2.RequestError) - raise native_error(str(exc), request=native_request) from exc - - def handler(native_request: Any) -> Any: - try: - response = router.handler(httpx_request(native_request, content=native_request.read())) - except httpx.RequestError as exc: - raise_native_request_error(exc, native_request) - return httpx2_response(response, native_request=native_request, content=response.read()) - - async def async_handler(native_request: Any) -> Any: - try: - response = await router.async_handler(httpx_request(native_request, content=await native_request.aread())) - except httpx.RequestError as exc: - raise_native_request_error(exc, native_request) - return httpx2_response(response, native_request=native_request, content=await response.aread()) - - # Match RESPX's own patch point. This also catches clients created inside a test, - # while leaving every non-RESPX test on its ordinary transport. - def sync_transport(*_args: Any) -> Any: - return httpx2.MockTransport(handler) - - def async_transport(*_args: Any) -> Any: - return httpx2.MockTransport(async_handler) - - monkeypatch.setattr(httpx2.Client, "_transport_for_url", sync_transport) - monkeypatch.setattr(httpx2.AsyncClient, "_transport_for_url", async_transport) - - if replace_sdk_defaults: - import openai._base_client as base_client - - # Clients constructed inside a RESPX test should exercise the same native - # path as the shared fixtures; the base-compatibility tests opt out. - monkeypatch.setattr(base_client, "SyncHttpxClientWrapper", DefaultHttpx2Client) - monkeypatch.setattr(base_client, "AsyncHttpxClientWrapper", DefaultAsyncHttpx2Client) diff --git a/tests/api_resources/audio/test_speech.py b/tests/api_resources/audio/test_speech.py index 9ad6d28316..76257dc4a7 100644 --- a/tests/api_resources/audio/test_speech.py +++ b/tests/api_resources/audio/test_speech.py @@ -5,13 +5,13 @@ import os from typing import Any, cast -import httpx +import httpx2 import pytest -from respx import MockRouter import openai._legacy_response as _legacy_response from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type +from tests.respx2 import MockRouter # pyright: reportDeprecated=false @@ -22,9 +22,9 @@ class TestSpeech: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_create(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_method_create(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/audio/speech").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) speech = client.audio.speech.create( input="string", model="tts-1", @@ -34,9 +34,9 @@ def test_method_create(self, client: OpenAI, respx_mock: MockRouter) -> None: assert speech.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_create_with_all_params(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_method_create_with_all_params(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/audio/speech").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) speech = client.audio.speech.create( input="string", model="tts-1", @@ -50,9 +50,9 @@ def test_method_create_with_all_params(self, client: OpenAI, respx_mock: MockRou assert speech.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - def test_raw_response_create(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_raw_response_create(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/audio/speech").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = client.audio.speech.with_raw_response.create( input="string", @@ -66,9 +66,9 @@ def test_raw_response_create(self, client: OpenAI, respx_mock: MockRouter) -> No assert_matches_type(_legacy_response.HttpxBinaryResponseContent, speech, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_streaming_response_create(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_streaming_response_create(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/audio/speech").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) with client.audio.speech.with_streaming_response.create( input="string", model="tts-1", @@ -89,9 +89,9 @@ class TestAsyncSpeech: ) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_create(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_method_create(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/audio/speech").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) speech = await async_client.audio.speech.create( input="string", model="tts-1", @@ -101,9 +101,9 @@ async def test_method_create(self, async_client: AsyncOpenAI, respx_mock: MockRo assert speech.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_create_with_all_params(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/audio/speech").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) speech = await async_client.audio.speech.create( input="string", model="tts-1", @@ -117,9 +117,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpenAI, re assert speech.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_create(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_raw_response_create(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/audio/speech").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = await async_client.audio.speech.with_raw_response.create( input="string", @@ -133,9 +133,9 @@ async def test_raw_response_create(self, async_client: AsyncOpenAI, respx_mock: assert_matches_type(_legacy_response.HttpxBinaryResponseContent, speech, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_streaming_response_create(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_streaming_response_create(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/audio/speech").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) async with async_client.audio.speech.with_streaming_response.create( input="string", model="tts-1", diff --git a/tests/api_resources/containers/files/test_content.py b/tests/api_resources/containers/files/test_content.py index aa282e8c04..b71577f8a8 100644 --- a/tests/api_resources/containers/files/test_content.py +++ b/tests/api_resources/containers/files/test_content.py @@ -5,13 +5,13 @@ import os from typing import Any, cast -import httpx +import httpx2 import pytest -from respx import MockRouter import openai._legacy_response as _legacy_response from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type +from tests.respx2 import MockRouter # pyright: reportDeprecated=false @@ -22,10 +22,10 @@ class TestContent: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/containers/container_id/files/file_id/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) + @pytest.mark.respx2(base_url=base_url) + def test_method_retrieve(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/containers/container_id/files/file_id/content").mock( + return_value=httpx2.Response(200, json={"foo": "bar"}) ) content = client.containers.files.content.retrieve( file_id="file_id", @@ -35,10 +35,10 @@ def test_method_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: assert content.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - def test_raw_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/containers/container_id/files/file_id/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) + @pytest.mark.respx2(base_url=base_url) + def test_raw_response_retrieve(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/containers/container_id/files/file_id/content").mock( + return_value=httpx2.Response(200, json={"foo": "bar"}) ) response = client.containers.files.content.with_raw_response.retrieve( @@ -52,10 +52,10 @@ def test_raw_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> assert_matches_type(_legacy_response.HttpxBinaryResponseContent, content, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_streaming_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/containers/container_id/files/file_id/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) + @pytest.mark.respx2(base_url=base_url) + def test_streaming_response_retrieve(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/containers/container_id/files/file_id/content").mock( + return_value=httpx2.Response(200, json={"foo": "bar"}) ) with client.containers.files.content.with_streaming_response.retrieve( file_id="file_id", @@ -70,7 +70,7 @@ def test_streaming_response_retrieve(self, client: OpenAI, respx_mock: MockRoute assert cast(Any, response.is_closed) is True @parametrize - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) def test_path_params_retrieve(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): client.containers.files.content.with_raw_response.retrieve( @@ -91,10 +91,10 @@ class TestAsyncContent: ) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/containers/container_id/files/file_id/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) + @pytest.mark.respx2(base_url=base_url) + async def test_method_retrieve(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/containers/container_id/files/file_id/content").mock( + return_value=httpx2.Response(200, json={"foo": "bar"}) ) content = await async_client.containers.files.content.retrieve( file_id="file_id", @@ -104,10 +104,10 @@ async def test_method_retrieve(self, async_client: AsyncOpenAI, respx_mock: Mock assert content.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/containers/container_id/files/file_id/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) + @pytest.mark.respx2(base_url=base_url) + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/containers/container_id/files/file_id/content").mock( + return_value=httpx2.Response(200, json={"foo": "bar"}) ) response = await async_client.containers.files.content.with_raw_response.retrieve( @@ -121,10 +121,10 @@ async def test_raw_response_retrieve(self, async_client: AsyncOpenAI, respx_mock assert_matches_type(_legacy_response.HttpxBinaryResponseContent, content, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/containers/container_id/files/file_id/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) + @pytest.mark.respx2(base_url=base_url) + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/containers/container_id/files/file_id/content").mock( + return_value=httpx2.Response(200, json={"foo": "bar"}) ) async with async_client.containers.files.content.with_streaming_response.retrieve( file_id="file_id", @@ -139,7 +139,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI, resp assert cast(Any, response.is_closed) is True @parametrize - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): await async_client.containers.files.content.with_raw_response.retrieve( diff --git a/tests/api_resources/realtime/test_calls.py b/tests/api_resources/realtime/test_calls.py index 1b70d02f9c..cd5466917c 100644 --- a/tests/api_resources/realtime/test_calls.py +++ b/tests/api_resources/realtime/test_calls.py @@ -5,13 +5,13 @@ import os from typing import Any, cast -import httpx +import httpx2 import pytest -from respx import MockRouter import openai._legacy_response as _legacy_response from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type +from tests.respx2 import MockRouter # pyright: reportDeprecated=false @@ -22,9 +22,9 @@ class TestCalls: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_create(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_method_create(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/realtime/calls").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) call = client.realtime.calls.create( sdp="sdp", ) @@ -32,9 +32,9 @@ def test_method_create(self, client: OpenAI, respx_mock: MockRouter) -> None: assert call.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_create_with_all_params(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_method_create_with_all_params(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/realtime/calls").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) call = client.realtime.calls.create( sdp="sdp", session={ @@ -102,9 +102,9 @@ def test_method_create_with_all_params(self, client: OpenAI, respx_mock: MockRou assert call.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - def test_raw_response_create(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_raw_response_create(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/realtime/calls").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = client.realtime.calls.with_raw_response.create( sdp="sdp", @@ -116,9 +116,9 @@ def test_raw_response_create(self, client: OpenAI, respx_mock: MockRouter) -> No assert_matches_type(_legacy_response.HttpxBinaryResponseContent, call, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_streaming_response_create(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_streaming_response_create(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/realtime/calls").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) with client.realtime.calls.with_streaming_response.create( sdp="sdp", ) as response: @@ -370,9 +370,9 @@ class TestAsyncCalls: ) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_create(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_method_create(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/realtime/calls").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) call = await async_client.realtime.calls.create( sdp="sdp", ) @@ -380,9 +380,9 @@ async def test_method_create(self, async_client: AsyncOpenAI, respx_mock: MockRo assert call.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_create_with_all_params(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/realtime/calls").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) call = await async_client.realtime.calls.create( sdp="sdp", session={ @@ -450,9 +450,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpenAI, re assert call.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_create(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_raw_response_create(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/realtime/calls").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = await async_client.realtime.calls.with_raw_response.create( sdp="sdp", @@ -464,9 +464,9 @@ async def test_raw_response_create(self, async_client: AsyncOpenAI, respx_mock: assert_matches_type(_legacy_response.HttpxBinaryResponseContent, call, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_streaming_response_create(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_streaming_response_create(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.post("/realtime/calls").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) async with async_client.realtime.calls.with_streaming_response.create( sdp="sdp", ) as response: diff --git a/tests/api_resources/skills/test_content.py b/tests/api_resources/skills/test_content.py index 91cefe204d..04d1d0c67a 100644 --- a/tests/api_resources/skills/test_content.py +++ b/tests/api_resources/skills/test_content.py @@ -5,13 +5,13 @@ import os from typing import Any, cast -import httpx +import httpx2 import pytest -from respx import MockRouter import openai._legacy_response as _legacy_response from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type +from tests.respx2 import MockRouter # pyright: reportDeprecated=false @@ -22,9 +22,9 @@ class TestContent: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/skills/skill_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_method_retrieve(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/skills/skill_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) content = client.skills.content.retrieve( "skill_123", ) @@ -32,9 +32,9 @@ def test_method_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: assert content.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - def test_raw_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/skills/skill_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_raw_response_retrieve(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/skills/skill_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = client.skills.content.with_raw_response.retrieve( "skill_123", @@ -46,9 +46,9 @@ def test_raw_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> assert_matches_type(_legacy_response.HttpxBinaryResponseContent, content, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_streaming_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/skills/skill_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_streaming_response_retrieve(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/skills/skill_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) with client.skills.content.with_streaming_response.retrieve( "skill_123", ) as response: @@ -61,7 +61,7 @@ def test_streaming_response_retrieve(self, client: OpenAI, respx_mock: MockRoute assert cast(Any, response.is_closed) is True @parametrize - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) def test_path_params_retrieve(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): client.skills.content.with_raw_response.retrieve( @@ -75,9 +75,9 @@ class TestAsyncContent: ) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/skills/skill_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_method_retrieve(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/skills/skill_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) content = await async_client.skills.content.retrieve( "skill_123", ) @@ -85,9 +85,9 @@ async def test_method_retrieve(self, async_client: AsyncOpenAI, respx_mock: Mock assert content.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/skills/skill_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/skills/skill_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = await async_client.skills.content.with_raw_response.retrieve( "skill_123", @@ -99,9 +99,9 @@ async def test_raw_response_retrieve(self, async_client: AsyncOpenAI, respx_mock assert_matches_type(_legacy_response.HttpxBinaryResponseContent, content, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/skills/skill_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/skills/skill_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) async with async_client.skills.content.with_streaming_response.retrieve( "skill_123", ) as response: @@ -114,7 +114,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI, resp assert cast(Any, response.is_closed) is True @parametrize - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): await async_client.skills.content.with_raw_response.retrieve( diff --git a/tests/api_resources/skills/versions/test_content.py b/tests/api_resources/skills/versions/test_content.py index 8f98effade..2c0b453c6b 100644 --- a/tests/api_resources/skills/versions/test_content.py +++ b/tests/api_resources/skills/versions/test_content.py @@ -5,13 +5,13 @@ import os from typing import Any, cast -import httpx +import httpx2 import pytest -from respx import MockRouter import openai._legacy_response as _legacy_response from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type +from tests.respx2 import MockRouter # pyright: reportDeprecated=false @@ -22,10 +22,10 @@ class TestContent: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/skills/skill_123/versions/version/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) + @pytest.mark.respx2(base_url=base_url) + def test_method_retrieve(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/skills/skill_123/versions/version/content").mock( + return_value=httpx2.Response(200, json={"foo": "bar"}) ) content = client.skills.versions.content.retrieve( version="version", @@ -35,10 +35,10 @@ def test_method_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: assert content.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - def test_raw_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/skills/skill_123/versions/version/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) + @pytest.mark.respx2(base_url=base_url) + def test_raw_response_retrieve(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/skills/skill_123/versions/version/content").mock( + return_value=httpx2.Response(200, json={"foo": "bar"}) ) response = client.skills.versions.content.with_raw_response.retrieve( @@ -52,10 +52,10 @@ def test_raw_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> assert_matches_type(_legacy_response.HttpxBinaryResponseContent, content, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_streaming_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/skills/skill_123/versions/version/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) + @pytest.mark.respx2(base_url=base_url) + def test_streaming_response_retrieve(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/skills/skill_123/versions/version/content").mock( + return_value=httpx2.Response(200, json={"foo": "bar"}) ) with client.skills.versions.content.with_streaming_response.retrieve( version="version", @@ -70,7 +70,7 @@ def test_streaming_response_retrieve(self, client: OpenAI, respx_mock: MockRoute assert cast(Any, response.is_closed) is True @parametrize - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) def test_path_params_retrieve(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): client.skills.versions.content.with_raw_response.retrieve( @@ -91,10 +91,10 @@ class TestAsyncContent: ) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/skills/skill_123/versions/version/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) + @pytest.mark.respx2(base_url=base_url) + async def test_method_retrieve(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/skills/skill_123/versions/version/content").mock( + return_value=httpx2.Response(200, json={"foo": "bar"}) ) content = await async_client.skills.versions.content.retrieve( version="version", @@ -104,10 +104,10 @@ async def test_method_retrieve(self, async_client: AsyncOpenAI, respx_mock: Mock assert content.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/skills/skill_123/versions/version/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) + @pytest.mark.respx2(base_url=base_url) + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/skills/skill_123/versions/version/content").mock( + return_value=httpx2.Response(200, json={"foo": "bar"}) ) response = await async_client.skills.versions.content.with_raw_response.retrieve( @@ -121,10 +121,10 @@ async def test_raw_response_retrieve(self, async_client: AsyncOpenAI, respx_mock assert_matches_type(_legacy_response.HttpxBinaryResponseContent, content, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/skills/skill_123/versions/version/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) + @pytest.mark.respx2(base_url=base_url) + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/skills/skill_123/versions/version/content").mock( + return_value=httpx2.Response(200, json={"foo": "bar"}) ) async with async_client.skills.versions.content.with_streaming_response.retrieve( version="version", @@ -139,7 +139,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI, resp assert cast(Any, response.is_closed) is True @parametrize - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): await async_client.skills.versions.content.with_raw_response.retrieve( diff --git a/tests/api_resources/test_files.py b/tests/api_resources/test_files.py index d8eec954f0..2b820c0265 100644 --- a/tests/api_resources/test_files.py +++ b/tests/api_resources/test_files.py @@ -5,14 +5,14 @@ import os from typing import Any, cast -import httpx +import httpx2 import pytest -from respx import MockRouter import openai._legacy_response as _legacy_response from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type from openai.types import FileObject, FileDeleted +from tests.respx2 import MockRouter from openai.pagination import SyncCursorPage, AsyncCursorPage # pyright: reportDeprecated=false @@ -181,9 +181,9 @@ def test_path_params_delete(self, client: OpenAI) -> None: ) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_content(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_method_content(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/files/string/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) file = client.files.content( "string", ) @@ -191,9 +191,9 @@ def test_method_content(self, client: OpenAI, respx_mock: MockRouter) -> None: assert file.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - def test_raw_response_content(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_raw_response_content(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/files/string/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = client.files.with_raw_response.content( "string", @@ -205,9 +205,9 @@ def test_raw_response_content(self, client: OpenAI, respx_mock: MockRouter) -> N assert_matches_type(_legacy_response.HttpxBinaryResponseContent, file, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_streaming_response_content(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_streaming_response_content(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/files/string/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) with client.files.with_streaming_response.content( "string", ) as response: @@ -220,7 +220,7 @@ def test_streaming_response_content(self, client: OpenAI, respx_mock: MockRouter assert cast(Any, response.is_closed) is True @parametrize - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) def test_path_params_content(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): client.files.with_raw_response.content( @@ -434,9 +434,9 @@ async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: ) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_content(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_method_content(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/files/string/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) file = await async_client.files.content( "string", ) @@ -444,9 +444,9 @@ async def test_method_content(self, async_client: AsyncOpenAI, respx_mock: MockR assert file.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_content(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_raw_response_content(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/files/string/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = await async_client.files.with_raw_response.content( "string", @@ -458,9 +458,9 @@ async def test_raw_response_content(self, async_client: AsyncOpenAI, respx_mock: assert_matches_type(_legacy_response.HttpxBinaryResponseContent, file, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_streaming_response_content(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_streaming_response_content(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/files/string/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) async with async_client.files.with_streaming_response.content( "string", ) as response: @@ -473,7 +473,7 @@ async def test_streaming_response_content(self, async_client: AsyncOpenAI, respx assert cast(Any, response.is_closed) is True @parametrize - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_path_params_content(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): await async_client.files.with_raw_response.content( diff --git a/tests/api_resources/test_videos.py b/tests/api_resources/test_videos.py index 8e147c6572..71e87a4e4a 100644 --- a/tests/api_resources/test_videos.py +++ b/tests/api_resources/test_videos.py @@ -5,9 +5,8 @@ import os from typing import Any, cast -import httpx +import httpx2 import pytest -from respx import MockRouter import openai._legacy_response as _legacy_response from openai import OpenAI, AsyncOpenAI @@ -18,6 +17,7 @@ VideoGetCharacterResponse, VideoCreateCharacterResponse, ) +from tests.respx2 import MockRouter from openai._utils import assert_signatures_in_sync from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage @@ -216,9 +216,9 @@ def test_streaming_response_create_character(self, client: OpenAI) -> None: assert cast(Any, response.is_closed) is True @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_download_content(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_method_download_content(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/videos/video_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) video = client.videos.download_content( video_id="video_123", ) @@ -226,9 +226,9 @@ def test_method_download_content(self, client: OpenAI, respx_mock: MockRouter) - assert video.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_download_content_with_all_params(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_method_download_content_with_all_params(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/videos/video_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) video = client.videos.download_content( video_id="video_123", variant="video", @@ -237,9 +237,9 @@ def test_method_download_content_with_all_params(self, client: OpenAI, respx_moc assert video.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - def test_raw_response_download_content(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_raw_response_download_content(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/videos/video_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = client.videos.with_raw_response.download_content( video_id="video_123", @@ -251,9 +251,9 @@ def test_raw_response_download_content(self, client: OpenAI, respx_mock: MockRou assert_matches_type(_legacy_response.HttpxBinaryResponseContent, video, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - def test_streaming_response_download_content(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_streaming_response_download_content(self, client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/videos/video_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) with client.videos.with_streaming_response.download_content( video_id="video_123", ) as response: @@ -266,7 +266,7 @@ def test_streaming_response_download_content(self, client: OpenAI, respx_mock: M assert cast(Any, response.is_closed) is True @parametrize - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) def test_path_params_download_content(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `video_id` but received ''"): client.videos.with_raw_response.download_content( @@ -617,9 +617,9 @@ async def test_streaming_response_create_character(self, async_client: AsyncOpen assert cast(Any, response.is_closed) is True @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_download_content(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_method_download_content(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/videos/video_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) video = await async_client.videos.download_content( video_id="video_123", ) @@ -627,11 +627,11 @@ async def test_method_download_content(self, async_client: AsyncOpenAI, respx_mo assert video.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_method_download_content_with_all_params( - self, async_client: AsyncOpenAI, respx_mock: MockRouter + self, async_client: AsyncOpenAI, respx2_mock: MockRouter ) -> None: - respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + respx2_mock.get("/videos/video_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) video = await async_client.videos.download_content( video_id="video_123", variant="video", @@ -640,9 +640,9 @@ async def test_method_download_content_with_all_params( assert video.json() == {"foo": "bar"} @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_download_content(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_raw_response_download_content(self, async_client: AsyncOpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/videos/video_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = await async_client.videos.with_raw_response.download_content( video_id="video_123", @@ -654,9 +654,11 @@ async def test_raw_response_download_content(self, async_client: AsyncOpenAI, re assert_matches_type(_legacy_response.HttpxBinaryResponseContent, video, path=["response"]) @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_streaming_response_download_content(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_streaming_response_download_content( + self, async_client: AsyncOpenAI, respx2_mock: MockRouter + ) -> None: + respx2_mock.get("/videos/video_123/content").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) async with async_client.videos.with_streaming_response.download_content( video_id="video_123", ) as response: @@ -669,7 +671,7 @@ async def test_streaming_response_download_content(self, async_client: AsyncOpen assert cast(Any, response.is_closed) is True @parametrize - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_path_params_download_content(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `video_id` but received ''"): await async_client.videos.with_raw_response.download_content( diff --git a/tests/conftest.py b/tests/conftest.py index 8128f1515b..ff3671cb0c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,22 +3,21 @@ from __future__ import annotations import os -import sys import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator -import httpx +import httpx2 import pytest from pytest_asyncio import is_async_test from openai import OpenAI, AsyncOpenAI, DefaultHttpx2Client, DefaultAioHttpClient, DefaultAsyncHttpx2Client from openai._utils import is_dict -from ._httpx2_respx import enable_httpx2_respx - if TYPE_CHECKING: from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] +pytest_plugins = ["tests.respx2.plugin"] + pytest.register_assert_rewrite("tests.utils") logging.getLogger("openai").setLevel(logging.DEBUG) @@ -34,7 +33,7 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: # RESPX cannot mock requests made by the aiohttp adapter. for item in items: - if "respx_mock" not in item.fixturenames: + if "respx2_mock" not in item.fixturenames: continue if "async_client" not in item.fixturenames: @@ -45,25 +44,16 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: 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")) + item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx2_mock")) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -test_http_client = os.environ.get("OPENAI_TEST_HTTP_CLIENT", "httpx") +test_http_client = os.environ.get("OPENAI_TEST_HTTP_CLIENT", "httpx2") api_key = "My API Key" admin_api_key = "My Admin API Key" -@pytest.fixture(autouse=True) -def patch_httpx2_respx(request: FixtureRequest, monkeypatch: pytest.MonkeyPatch) -> None: - if test_http_client != "httpx2" or "respx_mock" not in request.fixturenames: - return - - router = request.getfixturevalue("respx_mock") - enable_httpx2_respx(router, monkeypatch, replace_sdk_defaults=request.path.name != "test_httpx2_base.py") - - @pytest.fixture(scope="session") def client(request: FixtureRequest) -> Iterator[OpenAI]: strict = getattr(request, "param", True) @@ -88,7 +78,7 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncOpenAI]: # defaults strict = True - http_client: None | httpx.AsyncClient = None + http_client: None | httpx2.AsyncClient = None if isinstance(param, bool): strict = param @@ -96,11 +86,8 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncOpenAI]: strict = param.get("strict", True) assert isinstance(strict, bool) - http_client_type = param.get("http_client", "httpx") + http_client_type = param.get("http_client", "httpx2") if http_client_type == "aiohttp": - if sys.version_info < (3, 10): - pytest.skip("the aiohttp client requires Python 3.10 or later") - http_client = DefaultAioHttpClient() else: raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") diff --git a/tests/lib/chat/test_completions.py b/tests/lib/chat/test_completions.py index 741f5eaa75..609c4dbe4e 100644 --- a/tests/lib/chat/test_completions.py +++ b/tests/lib/chat/test_completions.py @@ -5,12 +5,12 @@ from typing_extensions import Literal, TypeVar import pytest -from respx import MockRouter from pydantic import Field, BaseModel from inline_snapshot import snapshot import openai from openai import OpenAI, AsyncOpenAI +from tests.respx2 import MockRouter from openai._utils import assert_signatures_in_sync from openai._compat import PYDANTIC_V1 @@ -28,8 +28,8 @@ # `OPENAI_LIVE=1 pytest --inline-snapshot=fix -p no:xdist -o addopts=""` -@pytest.mark.respx(base_url=base_url) -def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_nothing(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: completion = make_snapshot_request( lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", @@ -45,7 +45,7 @@ def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pyte ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(completion, monkeypatch) == snapshot( @@ -93,8 +93,8 @@ def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pyte ) -@pytest.mark.respx(base_url=base_url) -def test_parse_pydantic_model(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_pydantic_model(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: class Location(BaseModel): city: str temperature: float @@ -116,7 +116,7 @@ class Location(BaseModel): ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(completion, monkeypatch) == snapshot( @@ -163,9 +163,9 @@ class Location(BaseModel): ) -@pytest.mark.respx(base_url=base_url) +@pytest.mark.respx2(base_url=base_url) def test_parse_pydantic_model_optional_default( - client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch + client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch ) -> None: class Location(BaseModel): city: str @@ -188,7 +188,7 @@ class Location(BaseModel): ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(completion, monkeypatch) == snapshot( @@ -235,8 +235,8 @@ class Location(BaseModel): ) -@pytest.mark.respx(base_url=base_url) -def test_parse_pydantic_model_enum(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_pydantic_model_enum(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: class Color(Enum): """The detected color""" @@ -264,7 +264,7 @@ class ColorDetection(BaseModel): ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(completion.choices[0], monkeypatch) == snapshot( @@ -288,9 +288,9 @@ class ColorDetection(BaseModel): ) -@pytest.mark.respx(base_url=base_url) +@pytest.mark.respx2(base_url=base_url) def test_parse_pydantic_model_multiple_choices( - client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch + client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch ) -> None: class Location(BaseModel): city: str @@ -314,7 +314,7 @@ class Location(BaseModel): ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(completion.choices, monkeypatch) == snapshot( @@ -370,9 +370,9 @@ class Location(BaseModel): ) -@pytest.mark.respx(base_url=base_url) +@pytest.mark.respx2(base_url=base_url) @pytest.mark.skipif(PYDANTIC_V1, reason="dataclasses only supported in v2") -def test_parse_pydantic_dataclass(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +def test_parse_pydantic_dataclass(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: from pydantic.dataclasses import dataclass @dataclass @@ -395,7 +395,7 @@ class CalendarEvent: ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(completion, monkeypatch) == snapshot( @@ -442,8 +442,10 @@ class CalendarEvent: ) -@pytest.mark.respx(base_url=base_url) -def test_pydantic_tool_model_all_types(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_pydantic_tool_model_all_types( + client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch +) -> None: completion = make_snapshot_request( lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", @@ -461,7 +463,7 @@ def test_pydantic_tool_model_all_types(client: OpenAI, respx_mock: MockRouter, m ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(completion.choices[0], monkeypatch) == snapshot( @@ -522,8 +524,8 @@ def test_pydantic_tool_model_all_types(client: OpenAI, respx_mock: MockRouter, m ) -@pytest.mark.respx(base_url=base_url) -def test_parse_max_tokens_reached(client: OpenAI, respx_mock: MockRouter) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_max_tokens_reached(client: OpenAI, respx2_mock: MockRouter) -> None: class Location(BaseModel): city: str temperature: float @@ -547,12 +549,12 @@ class Location(BaseModel): ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) -@pytest.mark.respx(base_url=base_url) -def test_parse_pydantic_model_refusal(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_pydantic_model_refusal(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: class Location(BaseModel): city: str temperature: float @@ -574,7 +576,7 @@ class Location(BaseModel): ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(completion.choices, monkeypatch) == snapshot( @@ -600,8 +602,8 @@ class Location(BaseModel): ) -@pytest.mark.respx(base_url=base_url) -def test_parse_pydantic_tool(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_pydantic_tool(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: class GetWeatherArgs(BaseModel): city: str country: str @@ -625,7 +627,7 @@ class GetWeatherArgs(BaseModel): ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(completion.choices, monkeypatch) == snapshot( @@ -661,8 +663,10 @@ class GetWeatherArgs(BaseModel): ) -@pytest.mark.respx(base_url=base_url) -def test_parse_multiple_pydantic_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_multiple_pydantic_tools( + client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch +) -> None: class GetWeatherArgs(BaseModel): """Get the temperature for the given country/city combo""" @@ -699,7 +703,7 @@ class GetStockPrice(BaseModel): ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(completion.choices, monkeypatch) == snapshot( @@ -744,8 +748,8 @@ class GetStockPrice(BaseModel): ) -@pytest.mark.respx(base_url=base_url) -def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_strict_tools(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: completion = make_snapshot_request( lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", @@ -782,7 +786,7 @@ def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(completion.choices, monkeypatch) == snapshot( @@ -837,8 +841,8 @@ def test_parse_non_strict_tools(client: OpenAI) -> None: ) -@pytest.mark.respx(base_url=base_url) -def test_parse_pydantic_raw_response(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_pydantic_raw_response(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: class Location(BaseModel): city: str temperature: float @@ -860,7 +864,7 @@ class Location(BaseModel): ), path="/chat/completions", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert response.http_request.headers.get("x-stainless-helper-method") == "chat.completions.parse" @@ -912,10 +916,10 @@ class Location(BaseModel): ) -@pytest.mark.respx(base_url=base_url) +@pytest.mark.respx2(base_url=base_url) @pytest.mark.asyncio async def test_async_parse_pydantic_raw_response( - async_client: AsyncOpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch + async_client: AsyncOpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch ) -> None: class Location(BaseModel): city: str @@ -938,7 +942,7 @@ class Location(BaseModel): ), path="/chat/completions", mock_client=async_client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert response.http_request.headers.get("x-stainless-helper-method") == "chat.completions.parse" diff --git a/tests/lib/chat/test_completions_streaming.py b/tests/lib/chat/test_completions_streaming.py index 598a41ee2b..0b6200a71b 100644 --- a/tests/lib/chat/test_completions_streaming.py +++ b/tests/lib/chat/test_completions_streaming.py @@ -5,9 +5,8 @@ from typing_extensions import Literal, TypeVar import rich -import httpx +import httpx2 import pytest -from respx import MockRouter from pydantic import BaseModel from inline_snapshot import ( external, @@ -18,6 +17,7 @@ import openai from openai import OpenAI, AsyncOpenAI +from tests.respx2 import MockRouter from openai._utils import consume_sync_iterator, assert_signatures_in_sync from openai._compat import model_copy from openai.types.chat import ChatCompletionChunk @@ -43,8 +43,8 @@ # `OPENAI_LIVE=1 pytest --inline-snapshot=fix -p no:xdist -o addopts=""` -@pytest.mark.respx(base_url=base_url) -def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_nothing(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: listener = _make_stream_snapshot_request( lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", @@ -57,7 +57,7 @@ def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pyte ), content_snapshot=snapshot(external("e2aad469b71d*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot( @@ -94,8 +94,8 @@ def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pyte ) -@pytest.mark.respx(base_url=base_url) -def test_parse_pydantic_model(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_pydantic_model(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: class Location(BaseModel): city: str temperature: float @@ -120,7 +120,7 @@ def on_event(stream: ChatCompletionStream[Location], event: ChatCompletionStream ), content_snapshot=snapshot(external("7e5ea4d12e7c*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, on_event=on_event, ) @@ -191,9 +191,9 @@ def on_event(stream: ChatCompletionStream[Location], event: ChatCompletionStream ) -@pytest.mark.respx(base_url=base_url) +@pytest.mark.respx2(base_url=base_url) def test_parse_pydantic_model_multiple_choices( - client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch + client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch ) -> None: class Location(BaseModel): city: str @@ -214,7 +214,7 @@ class Location(BaseModel): ), content_snapshot=snapshot(external("a491adda08c3*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert [e.type for e in listener.events] == snapshot( @@ -371,8 +371,8 @@ class Location(BaseModel): ) -@pytest.mark.respx(base_url=base_url) -def test_parse_max_tokens_reached(client: OpenAI, respx_mock: MockRouter) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_max_tokens_reached(client: OpenAI, respx2_mock: MockRouter) -> None: class Location(BaseModel): city: str temperature: float @@ -393,12 +393,12 @@ class Location(BaseModel): ), content_snapshot=snapshot(external("4cc50a6135d2*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) -@pytest.mark.respx(base_url=base_url) -def test_parse_pydantic_model_refusal(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_pydantic_model_refusal(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: class Location(BaseModel): city: str temperature: float @@ -417,7 +417,7 @@ class Location(BaseModel): ), content_snapshot=snapshot(external("173417d55340*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(listener.get_event_by_type("refusal.done"), monkeypatch) == snapshot("""\ @@ -447,8 +447,8 @@ class Location(BaseModel): ) -@pytest.mark.respx(base_url=base_url) -def test_content_logprobs_events(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_content_logprobs_events(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: listener = _make_stream_snapshot_request( lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", @@ -462,7 +462,7 @@ def test_content_logprobs_events(client: OpenAI, respx_mock: MockRouter, monkeyp ), content_snapshot=snapshot(external("83b060bae42e*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj([e for e in listener.events if e.type.startswith("logprobs")], monkeypatch) == snapshot("""\ @@ -521,8 +521,8 @@ def test_content_logprobs_events(client: OpenAI, respx_mock: MockRouter, monkeyp """) -@pytest.mark.respx(base_url=base_url) -def test_refusal_logprobs_events(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_refusal_logprobs_events(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: class Location(BaseModel): city: str temperature: float @@ -542,7 +542,7 @@ class Location(BaseModel): ), content_snapshot=snapshot(external("569c877e6942*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj([e.type for e in listener.events if e.type.startswith("logprobs")], monkeypatch) == snapshot("""\ @@ -633,8 +633,8 @@ class Location(BaseModel): """) -@pytest.mark.respx(base_url=base_url) -def test_parse_pydantic_tool(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_pydantic_tool(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: class GetWeatherArgs(BaseModel): city: str country: str @@ -655,7 +655,7 @@ class GetWeatherArgs(BaseModel): ), content_snapshot=snapshot(external("c6aa7e397b71*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(listener.stream.current_completion_snapshot.choices, monkeypatch) == snapshot( @@ -725,8 +725,10 @@ class GetWeatherArgs(BaseModel): ) -@pytest.mark.respx(base_url=base_url) -def test_parse_multiple_pydantic_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_multiple_pydantic_tools( + client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch +) -> None: class GetWeatherArgs(BaseModel): """Get the temperature for the given country/city combo""" @@ -760,7 +762,7 @@ class GetStockPrice(BaseModel): ), content_snapshot=snapshot(external("f82268f2fefd*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(listener.stream.current_completion_snapshot.choices, monkeypatch) == snapshot( @@ -834,8 +836,8 @@ class GetStockPrice(BaseModel): ) -@pytest.mark.respx(base_url=base_url) -def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_parse_strict_tools(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: listener = _make_stream_snapshot_request( lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", @@ -869,7 +871,7 @@ def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: ), content_snapshot=snapshot(external("a247c49c5fcd*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(listener.stream.current_completion_snapshot.choices, monkeypatch) == snapshot( @@ -906,8 +908,8 @@ def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: ) -@pytest.mark.respx(base_url=base_url) -def test_non_pydantic_response_format(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_non_pydantic_response_format(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: listener = _make_stream_snapshot_request( lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", @@ -921,7 +923,7 @@ def test_non_pydantic_response_format(client: OpenAI, respx_mock: MockRouter, mo ), content_snapshot=snapshot(external("d61558011839*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot( @@ -952,9 +954,9 @@ def test_non_pydantic_response_format(client: OpenAI, respx_mock: MockRouter, mo ) -@pytest.mark.respx(base_url=base_url) +@pytest.mark.respx2(base_url=base_url) def test_allows_non_strict_tools_but_no_parsing( - client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch + client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch ) -> None: listener = _make_stream_snapshot_request( lambda c: c.chat.completions.stream( @@ -972,7 +974,7 @@ def test_allows_non_strict_tools_but_no_parsing( ), content_snapshot=snapshot(external("2018feb66ae1*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(listener.get_event_by_type("tool_calls.function.arguments.done"), monkeypatch) == snapshot("""\ @@ -1019,8 +1021,8 @@ def test_allows_non_strict_tools_but_no_parsing( ) -@pytest.mark.respx(base_url=base_url) -def test_chat_completion_state_helper(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_chat_completion_state_helper(client: OpenAI, respx2_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: state = ChatCompletionStreamState() def streamer(client: OpenAI) -> Iterator[ChatCompletionChunk]: @@ -1042,7 +1044,7 @@ def streamer(client: OpenAI) -> Iterator[ChatCompletionChunk]: streamer, content_snapshot=snapshot(external("e2aad469b71d*.bin")), mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert print_obj(state.get_final_completion().choices, monkeypatch) == snapshot( @@ -1104,7 +1106,7 @@ def _make_stream_snapshot_request( func: Callable[[OpenAI], ChatCompletionStreamManager[ResponseFormatT]], *, content_snapshot: Any, - respx_mock: MockRouter, + respx2_mock: MockRouter, mock_client: OpenAI, on_event: Callable[[ChatCompletionStream[ResponseFormatT], ChatCompletionStreamEvent[ResponseFormatT]], Any] | None = None, @@ -1112,22 +1114,22 @@ def _make_stream_snapshot_request( live = os.environ.get("OPENAI_LIVE") == "1" if live: - def _on_response(response: httpx.Response) -> None: + def _on_response(response: httpx2.Response) -> None: # update the content snapshot assert outsource(response.read()) == content_snapshot - respx_mock.stop() + respx2_mock.stop() client = OpenAI( - http_client=httpx.Client( + http_client=httpx2.Client( event_hooks={ "response": [_on_response], } ) ) else: - respx_mock.post("/chat/completions").mock( - return_value=httpx.Response( + respx2_mock.post("/chat/completions").mock( + return_value=httpx2.Response( 200, content=get_snapshot_value(content_snapshot), headers={"content-type": "text/event-stream"}, @@ -1153,28 +1155,28 @@ def _make_raw_stream_snapshot_request( func: Callable[[OpenAI], Iterator[ChatCompletionChunk]], *, content_snapshot: Any, - respx_mock: MockRouter, + respx2_mock: MockRouter, mock_client: OpenAI, ) -> None: live = os.environ.get("OPENAI_LIVE") == "1" if live: - def _on_response(response: httpx.Response) -> None: + def _on_response(response: httpx2.Response) -> None: # update the content snapshot assert outsource(response.read()) == content_snapshot - respx_mock.stop() + respx2_mock.stop() client = OpenAI( - http_client=httpx.Client( + http_client=httpx2.Client( event_hooks={ "response": [_on_response], } ) ) else: - respx_mock.post("/chat/completions").mock( - return_value=httpx.Response( + respx2_mock.post("/chat/completions").mock( + return_value=httpx2.Response( 200, content=get_snapshot_value(content_snapshot), headers={"content-type": "text/event-stream"}, diff --git a/tests/lib/responses/test_responses.py b/tests/lib/responses/test_responses.py index 4ed6dff47d..43879942b8 100644 --- a/tests/lib/responses/test_responses.py +++ b/tests/lib/responses/test_responses.py @@ -3,10 +3,10 @@ from typing_extensions import TypeVar import pytest -from respx import MockRouter from inline_snapshot import snapshot from openai import OpenAI, AsyncOpenAI +from tests.respx2 import MockRouter from openai._types import omit from openai._utils import assert_signatures_in_sync from openai._models import construct_type_unchecked @@ -25,8 +25,8 @@ # `OPENAI_LIVE=1 pytest --inline-snapshot=fix -p no:xdist -o addopts=""` -@pytest.mark.respx(base_url=base_url) -def test_output_text(client: OpenAI, respx_mock: MockRouter) -> None: +@pytest.mark.respx2(base_url=base_url) +def test_output_text(client: OpenAI, respx2_mock: MockRouter) -> None: response = make_snapshot_request( lambda c: c.responses.create( model="gpt-4o-mini", @@ -37,7 +37,7 @@ def test_output_text(client: OpenAI, respx_mock: MockRouter) -> None: ), path="/responses", mock_client=client, - respx_mock=respx_mock, + respx2_mock=respx2_mock, ) assert response.output_text == snapshot( diff --git a/tests/lib/snapshots.py b/tests/lib/snapshots.py index 91222acda1..4789596def 100644 --- a/tests/lib/snapshots.py +++ b/tests/lib/snapshots.py @@ -5,11 +5,11 @@ from typing import Any, Callable, Awaitable from typing_extensions import TypeVar -import httpx -from respx import MockRouter +import httpx2 from inline_snapshot import get_snapshot_value from openai import OpenAI, AsyncOpenAI +from tests.respx2 import MockRouter _T = TypeVar("_T") @@ -18,29 +18,29 @@ def make_snapshot_request( func: Callable[[OpenAI], _T], *, content_snapshot: Any, - respx_mock: MockRouter, + respx2_mock: MockRouter, mock_client: OpenAI, path: str, ) -> _T: live = os.environ.get("OPENAI_LIVE") == "1" if live: - def _on_response(response: httpx.Response) -> None: + def _on_response(response: httpx2.Response) -> None: # update the content snapshot assert json.dumps(json.loads(response.read())) == content_snapshot - respx_mock.stop() + respx2_mock.stop() client = OpenAI( - http_client=httpx.Client( + http_client=httpx2.Client( event_hooks={ "response": [_on_response], } ) ) else: - respx_mock.post(path).mock( - return_value=httpx.Response( + respx2_mock.post(path).mock( + return_value=httpx2.Response( 200, content=get_snapshot_value(content_snapshot), headers={"content-type": "application/json"}, @@ -61,29 +61,29 @@ async def make_async_snapshot_request( func: Callable[[AsyncOpenAI], Awaitable[_T]], *, content_snapshot: Any, - respx_mock: MockRouter, + respx2_mock: MockRouter, mock_client: AsyncOpenAI, path: str, ) -> _T: live = os.environ.get("OPENAI_LIVE") == "1" if live: - async def _on_response(response: httpx.Response) -> None: + async def _on_response(response: httpx2.Response) -> None: # update the content snapshot assert json.dumps(json.loads(await response.aread())) == content_snapshot - respx_mock.stop() + respx2_mock.stop() client = AsyncOpenAI( - http_client=httpx.AsyncClient( + http_client=httpx2.AsyncClient( event_hooks={ "response": [_on_response], } ) ) else: - respx_mock.post(path).mock( - return_value=httpx.Response( + respx2_mock.post(path).mock( + return_value=httpx2.Response( 200, content=get_snapshot_value(content_snapshot), headers={"content-type": "application/json"}, diff --git a/tests/lib/test_azure.py b/tests/lib/test_azure.py index 3e1d783e2c..691a911144 100644 --- a/tests/lib/test_azure.py +++ b/tests/lib/test_azure.py @@ -4,12 +4,12 @@ from typing import Union, cast from typing_extensions import Literal, Protocol -import httpx +import httpx2 import pytest -from respx import MockRouter from openai import OpenAIError from tests.utils import update_env +from tests.respx2 import MockRouter from openai._types import Omit from openai._utils import SensitiveHeadersFilter, is_dict from openai._models import FinalRequestOptions @@ -32,7 +32,7 @@ class MockRequestCall(Protocol): - request: httpx.Request + request: httpx2.Request @pytest.mark.parametrize("client", [sync_client, async_client]) @@ -91,11 +91,11 @@ def test_enforce_credentials_false_sync() -> None: ) -@pytest.mark.respx() -def test_enforce_credentials_false_sync_uses_default_api_key_header(respx_mock: MockRouter) -> None: - respx_mock.post( +@pytest.mark.respx2() +def test_enforce_credentials_false_sync_uses_default_api_key_header(respx2_mock: MockRouter) -> None: + respx2_mock.post( "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01" - ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + ).mock(return_value=httpx2.Response(200, json={"model": "gpt-4"})) with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): client = AzureOpenAI( @@ -109,16 +109,16 @@ def test_enforce_credentials_false_sync_uses_default_api_key_header(respx_mock: ) client.chat.completions.create(messages=[], model="gpt-4") - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert calls[0].request.headers.get("api-key") == "manual-api-key" assert calls[0].request.headers.get("Authorization") is None -@pytest.mark.respx() -def test_enforce_credentials_false_sync_uses_request_authorization_header(respx_mock: MockRouter) -> None: - respx_mock.post( +@pytest.mark.respx2() +def test_enforce_credentials_false_sync_uses_request_authorization_header(respx2_mock: MockRouter) -> None: + respx2_mock.post( "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01" - ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + ).mock(return_value=httpx2.Response(200, json={"model": "gpt-4"})) with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): client = AzureOpenAI( @@ -135,7 +135,7 @@ def test_enforce_credentials_false_sync_uses_request_authorization_header(respx_ extra_headers={"authorization": "Bearer manual-token"}, ) - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert calls[0].request.headers.get("Authorization") == "Bearer manual-token" assert calls[0].request.headers.get("api-key") is None @@ -165,11 +165,11 @@ def test_enforce_credentials_false_async() -> None: @pytest.mark.asyncio -@pytest.mark.respx() -async def test_enforce_credentials_false_async_uses_default_api_key_header(respx_mock: MockRouter) -> None: - respx_mock.post( +@pytest.mark.respx2() +async def test_enforce_credentials_false_async_uses_default_api_key_header(respx2_mock: MockRouter) -> None: + respx2_mock.post( "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01" - ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + ).mock(return_value=httpx2.Response(200, json={"model": "gpt-4"})) with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): client = AsyncAzureOpenAI( @@ -183,17 +183,17 @@ async def test_enforce_credentials_false_async_uses_default_api_key_header(respx ) await client.chat.completions.create(messages=[], model="gpt-4") - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert calls[0].request.headers.get("api-key") == "manual-api-key" assert calls[0].request.headers.get("Authorization") is None @pytest.mark.asyncio -@pytest.mark.respx() -async def test_enforce_credentials_false_async_uses_request_authorization_header(respx_mock: MockRouter) -> None: - respx_mock.post( +@pytest.mark.respx2() +async def test_enforce_credentials_false_async_uses_request_authorization_header(respx2_mock: MockRouter) -> None: + respx2_mock.post( "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01" - ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + ).mock(return_value=httpx2.Response(200, json={"model": "gpt-4"})) with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): client = AsyncAzureOpenAI( @@ -210,7 +210,7 @@ async def test_enforce_credentials_false_async_uses_request_authorization_header extra_headers={"authorization": "Bearer manual-token"}, ) - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert calls[0].request.headers.get("Authorization") == "Bearer manual-token" assert calls[0].request.headers.get("api-key") is None @@ -227,14 +227,14 @@ def test_enforce_credentials_true_async() -> None: ) -@pytest.mark.respx() -def test_client_token_provider_refresh_sync(respx_mock: MockRouter) -> None: - respx_mock.post( +@pytest.mark.respx2() +def test_client_token_provider_refresh_sync(respx2_mock: MockRouter) -> None: + respx2_mock.post( "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01" ).mock( side_effect=[ - httpx.Response(500, json={"error": "server error"}), - httpx.Response(200, json={"foo": "bar"}), + httpx2.Response(500, json={"error": "server error"}), + httpx2.Response(200, json={"foo": "bar"}), ] ) @@ -257,7 +257,7 @@ def token_provider() -> str: ) client.chat.completions.create(messages=[], model="gpt-4") - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert len(calls) == 2 @@ -266,14 +266,14 @@ def token_provider() -> str: @pytest.mark.asyncio -@pytest.mark.respx() -async def test_client_token_provider_refresh_async(respx_mock: MockRouter) -> None: - respx_mock.post( +@pytest.mark.respx2() +async def test_client_token_provider_refresh_async(respx2_mock: MockRouter) -> None: + respx2_mock.post( "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01" ).mock( side_effect=[ - httpx.Response(500, json={"error": "server error"}), - httpx.Response(200, json={"foo": "bar"}), + httpx2.Response(500, json={"error": "server error"}), + httpx2.Response(200, json={"foo": "bar"}), ] ) @@ -297,7 +297,7 @@ def token_provider() -> str: await client.chat.completions.create(messages=[], model="gpt-4") - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert len(calls) == 2 @@ -313,11 +313,11 @@ def logger_with_filter(self) -> logging.Logger: logger.addFilter(SensitiveHeadersFilter()) return logger - @pytest.mark.respx() - def test_azure_api_key_redacted(self, respx_mock: MockRouter, caplog: pytest.LogCaptureFixture) -> None: - respx_mock.post( + @pytest.mark.respx2() + def test_azure_api_key_redacted(self, respx2_mock: MockRouter, caplog: pytest.LogCaptureFixture) -> None: + respx2_mock.post( "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-06-01" - ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + ).mock(return_value=httpx2.Response(200, json={"model": "gpt-4"})) client = AzureOpenAI( api_version="2024-06-01", @@ -332,11 +332,11 @@ def test_azure_api_key_redacted(self, respx_mock: MockRouter, caplog: pytest.Log if is_dict(record.args) and record.args.get("headers") and is_dict(record.args["headers"]): assert record.args["headers"]["api-key"] == "" - @pytest.mark.respx() - def test_azure_bearer_token_redacted(self, respx_mock: MockRouter, caplog: pytest.LogCaptureFixture) -> None: - respx_mock.post( + @pytest.mark.respx2() + def test_azure_bearer_token_redacted(self, respx2_mock: MockRouter, caplog: pytest.LogCaptureFixture) -> None: + respx2_mock.post( "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-06-01" - ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + ).mock(return_value=httpx2.Response(200, json={"model": "gpt-4"})) client = AzureOpenAI( api_version="2024-06-01", @@ -352,11 +352,13 @@ def test_azure_bearer_token_redacted(self, respx_mock: MockRouter, caplog: pytes assert record.args["headers"]["Authorization"] == "" @pytest.mark.asyncio - @pytest.mark.respx() - async def test_azure_api_key_redacted_async(self, respx_mock: MockRouter, caplog: pytest.LogCaptureFixture) -> None: - respx_mock.post( + @pytest.mark.respx2() + async def test_azure_api_key_redacted_async( + self, respx2_mock: MockRouter, caplog: pytest.LogCaptureFixture + ) -> None: + respx2_mock.post( "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-06-01" - ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + ).mock(return_value=httpx2.Response(200, json={"model": "gpt-4"})) client = AsyncAzureOpenAI( api_version="2024-06-01", @@ -372,13 +374,13 @@ async def test_azure_api_key_redacted_async(self, respx_mock: MockRouter, caplog assert record.args["headers"]["api-key"] == "" @pytest.mark.asyncio - @pytest.mark.respx() + @pytest.mark.respx2() async def test_azure_bearer_token_redacted_async( - self, respx_mock: MockRouter, caplog: pytest.LogCaptureFixture + self, respx2_mock: MockRouter, caplog: pytest.LogCaptureFixture ) -> None: - respx_mock.post( + respx2_mock.post( "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-06-01" - ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + ).mock(return_value=httpx2.Response(200, json={"model": "gpt-4"})) client = AsyncAzureOpenAI( api_version="2024-06-01", diff --git a/tests/lib/test_bedrock.py b/tests/lib/test_bedrock.py index bf987b2fa9..96ebe80a10 100644 --- a/tests/lib/test_bedrock.py +++ b/tests/lib/test_bedrock.py @@ -4,17 +4,16 @@ from typing import Any, Union, Protocol, cast from pathlib import Path -import httpx +import httpx2 import pytest -from httpx import URL -from respx import MockRouter +from httpx2 import URL import openai.lib._bedrock_auth as bedrock_auth_module -from openai import OpenAIError, NotFoundError +from openai import OpenAIError, NotFoundError, DefaultHttpx2Client, DefaultAsyncHttpx2Client from tests.utils import update_env +from tests.respx2 import MockRouter from openai._types import Omit from openai.lib.bedrock import BedrockOpenAI, AsyncBedrockOpenAI -from tests._httpx2_respx import sync_http_client, async_http_client Client = Union[BedrockOpenAI, AsyncBedrockOpenAI] @@ -76,7 +75,7 @@ class MockRequestCall(Protocol): - request: httpx.Request + request: httpx2.Request class MockAwsCredentials: @@ -87,11 +86,11 @@ def __init__(self, access_key: str, secret_key: str, token: str | None = None) - def make_sync_client(**kwargs: Any) -> BedrockOpenAI: - return BedrockOpenAI(http_client=sync_http_client(trust_env=False), **kwargs) + return BedrockOpenAI(http_client=DefaultHttpx2Client(trust_env=False), **kwargs) def make_async_client(**kwargs: Any) -> AsyncBedrockOpenAI: - return AsyncBedrockOpenAI(http_client=async_http_client(trust_env=False), **kwargs) + return AsyncBedrockOpenAI(http_client=DefaultAsyncHttpx2Client(trust_env=False), **kwargs) def response_created_sse() -> str: @@ -133,14 +132,14 @@ def test_bedrock_config_precedence(client_cls: type[Client]) -> None: assert client.api_key == "explicit token" -@pytest.mark.respx() -def test_env_bearer_does_not_require_botocore(monkeypatch: pytest.MonkeyPatch, respx_mock: MockRouter) -> None: +@pytest.mark.respx2() +def test_env_bearer_does_not_require_botocore(monkeypatch: pytest.MonkeyPatch, respx2_mock: MockRouter) -> None: def load_botocore() -> None: raise AssertionError("bearer authentication must not import botocore") monkeypatch.setattr(bedrock_auth_module, "_load_botocore", load_botocore) - respx_mock.post("https://example.com/openai/v1/responses").mock( - return_value=httpx.Response(200, json=RESPONSE_BODY) + respx2_mock.post("https://example.com/openai/v1/responses").mock( + return_value=httpx2.Response(200, json=RESPONSE_BODY) ) with update_env( AWS_BEDROCK_BASE_URL="https://example.com/openai/v1", @@ -150,7 +149,7 @@ def load_botocore() -> None: client.responses.create(model="gpt-4o", input="hello") - request = cast("list[MockRequestCall]", respx_mock.calls)[0].request + request = cast("list[MockRequestCall]", respx2_mock.calls)[0].request assert request.headers["Authorization"] == "Bearer env token" @@ -165,11 +164,11 @@ def load_botocore() -> None: with update_env(AWS_BEDROCK_BASE_URL=Omit(), AWS_BEARER_TOKEN_BEDROCK="", AWS_REGION="us-east-1"): client = make_sync_client() with pytest.raises(OpenAIError, match="requires optional AWS dependencies"): - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) -@pytest.mark.respx() -def test_env_bearer_does_not_use_botocore_bearer_auth(monkeypatch: pytest.MonkeyPatch, respx_mock: MockRouter) -> None: +@pytest.mark.respx2() +def test_env_bearer_does_not_use_botocore_bearer_auth(monkeypatch: pytest.MonkeyPatch, respx2_mock: MockRouter) -> None: auth_module = pytest.importorskip("botocore.auth") calls = 0 real_add_auth = auth_module.BearerAuth.add_auth @@ -180,15 +179,15 @@ def add_auth(auth: object, request: object) -> None: real_add_auth(auth, request) monkeypatch.setattr(auth_module.BearerAuth, "add_auth", add_auth) - respx_mock.post("https://example.com/openai/v1/responses").mock( - return_value=httpx.Response(200, json=RESPONSE_BODY) + respx2_mock.post("https://example.com/openai/v1/responses").mock( + return_value=httpx2.Response(200, json=RESPONSE_BODY) ) with update_env(AWS_BEARER_TOKEN_BEDROCK="env token"): client = make_sync_client(base_url="https://example.com/openai/v1") client.responses.create(model="gpt-4o", input="hello") - request = cast("list[MockRequestCall]", respx_mock.calls)[0].request + request = cast("list[MockRequestCall]", respx2_mock.calls)[0].request assert request.headers["Authorization"] == "Bearer env token" assert calls == 0 @@ -322,57 +321,57 @@ def test_requires_refreshable_tokens_to_use_provider_option(client_cls: type[Cli ) -@pytest.mark.respx() -def test_token_provider_refresh_sync(respx_mock: MockRouter) -> None: - respx_mock.post("https://example.com/openai/v1/responses").mock( +@pytest.mark.respx2() +def test_token_provider_refresh_sync(respx2_mock: MockRouter) -> None: + respx2_mock.post("https://example.com/openai/v1/responses").mock( side_effect=[ - httpx.Response(500, json={"error": "server error"}), - httpx.Response(200, json=RESPONSE_BODY), + httpx2.Response(500, json={"error": "server error"}), + httpx2.Response(200, json=RESPONSE_BODY), ] ) tokens = iter(["first", "second"]) client = BedrockOpenAI( base_url="https://example.com/openai/v1", bedrock_token_provider=lambda: next(tokens), - http_client=sync_http_client(trust_env=False), + http_client=DefaultHttpx2Client(trust_env=False), max_retries=1, ) client.responses.create(model="gpt-4o", input="hello") - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert calls[0].request.headers["Authorization"] == "Bearer first" assert calls[1].request.headers["Authorization"] == "Bearer second" @pytest.mark.asyncio -@pytest.mark.respx() -async def test_token_provider_refresh_async(respx_mock: MockRouter) -> None: - respx_mock.post("https://example.com/openai/v1/responses").mock( +@pytest.mark.respx2() +async def test_token_provider_refresh_async(respx2_mock: MockRouter) -> None: + respx2_mock.post("https://example.com/openai/v1/responses").mock( side_effect=[ - httpx.Response(500, json={"error": "server error"}), - httpx.Response(200, json=RESPONSE_BODY), + httpx2.Response(500, json={"error": "server error"}), + httpx2.Response(200, json=RESPONSE_BODY), ] ) tokens = iter(["first", "second"]) client = AsyncBedrockOpenAI( base_url="https://example.com/openai/v1", bedrock_token_provider=lambda: next(tokens), - http_client=async_http_client(trust_env=False), + http_client=DefaultAsyncHttpx2Client(trust_env=False), max_retries=1, ) await client.responses.create(model="gpt-4o", input="hello") - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert calls[0].request.headers["Authorization"] == "Bearer first" assert calls[1].request.headers["Authorization"] == "Bearer second" -@pytest.mark.respx() -def test_explicit_aws_credentials_override_ambient_bearer(respx_mock: MockRouter) -> None: - respx_mock.post("https://example.com/openai/v1/responses").mock( - return_value=httpx.Response(200, json=RESPONSE_BODY) +@pytest.mark.respx2() +def test_explicit_aws_credentials_override_ambient_bearer(respx2_mock: MockRouter) -> None: + respx2_mock.post("https://example.com/openai/v1/responses").mock( + return_value=httpx2.Response(200, json=RESPONSE_BODY) ) with update_env(AWS_BEARER_TOKEN_BEDROCK="ambient token"): client = BedrockOpenAI( @@ -381,22 +380,22 @@ def test_explicit_aws_credentials_override_ambient_bearer(respx_mock: MockRouter aws_access_key_id="access key", aws_secret_access_key="secret key", aws_session_token="session token", - http_client=sync_http_client(trust_env=False), + http_client=DefaultHttpx2Client(trust_env=False), ) client.responses.create(model="gpt-4o", input="hello") - request = cast("list[MockRequestCall]", respx_mock.calls)[0].request + request = cast("list[MockRequestCall]", respx2_mock.calls)[0].request assert request.headers["Authorization"].startswith("AWS4-HMAC-SHA256 Credential=access key/") assert request.headers["X-Amz-Security-Token"] == "session token" -@pytest.mark.respx() -def test_aws_credentials_provider_refreshes_before_retries(respx_mock: MockRouter) -> None: - respx_mock.post("https://example.com/openai/v1/responses").mock( +@pytest.mark.respx2() +def test_aws_credentials_provider_refreshes_before_retries(respx2_mock: MockRouter) -> None: + respx2_mock.post("https://example.com/openai/v1/responses").mock( side_effect=[ - httpx.Response(500, json={"error": "server error"}), - httpx.Response(200, json=RESPONSE_BODY), + httpx2.Response(500, json={"error": "server error"}), + httpx2.Response(200, json=RESPONSE_BODY), ] ) credentials = iter( @@ -409,13 +408,13 @@ def test_aws_credentials_provider_refreshes_before_retries(respx_mock: MockRoute base_url="https://example.com/openai/v1", aws_region="us-east-1", aws_credentials_provider=lambda: next(credentials), - http_client=sync_http_client(trust_env=False), + http_client=DefaultHttpx2Client(trust_env=False), max_retries=1, ) client.responses.create(model="gpt-4o", input="hello") - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert "Credential=first access key/" in calls[0].request.headers["Authorization"] assert calls[0].request.headers["X-Amz-Security-Token"] == "first session token" assert "Credential=second access key/" in calls[1].request.headers["Authorization"] @@ -426,7 +425,7 @@ def test_preserves_token_provider_across_with_options() -> None: client = BedrockOpenAI( base_url="https://example.com/openai/v1", bedrock_token_provider=lambda: "provider token", - http_client=sync_http_client(trust_env=False), + http_client=DefaultHttpx2Client(trust_env=False), ) copied_client = client.with_options(timeout=1) @@ -435,65 +434,65 @@ def test_preserves_token_provider_across_with_options() -> None: def test_preserves_environment_bearer_across_with_options() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) with update_env(AWS_BEARER_TOKEN_BEDROCK="first token"): client = BedrockOpenAI( base_url="https://example.com/openai/v1", - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) with update_env(AWS_BEARER_TOKEN_BEDROCK="second token"): copied_client = client.with_options(timeout=1) - copied_client.get("/models", cast_to=httpx.Response) + copied_client.get("/models", cast_to=httpx2.Response) assert copied_client.api_key == "first token" assert requests[0].headers["Authorization"] == "Bearer first token" def test_environment_bearer_routing_copy_remains_mutable() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) with update_env(AWS_BEARER_TOKEN_BEDROCK="first token"): client = BedrockOpenAI( aws_region="us-east-1", - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) copied_client = client.with_options(aws_region="us-west-2") copied_client.api_key = "second token" - copied_client.get("/models", cast_to=httpx.Response) + copied_client.get("/models", cast_to=httpx2.Response) assert copied_client.api_key == "second token" assert requests[0].headers["Authorization"] == "Bearer second token" def test_legacy_api_key_mutation_updates_requests_and_copies() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) client = BedrockOpenAI( base_url="https://example.com/openai/v1", api_key="first token", - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) client.api_key = "second token" - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) copied_client = client.with_options(timeout=1) - copied_client.get("/models", cast_to=httpx.Response) + copied_client.get("/models", cast_to=httpx2.Response) client.api_key = "first token" reverted_client = client.with_options(timeout=2) - reverted_client.get("/models", cast_to=httpx.Response) + reverted_client.get("/models", cast_to=httpx2.Response) assert copied_client.api_key == "second token" assert reverted_client.api_key == "first token" @@ -505,21 +504,21 @@ def handler(request: httpx.Request) -> httpx.Response: def test_legacy_api_key_mutation_switches_aws_client_to_bearer() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) client = BedrockOpenAI( base_url="https://example.com/openai/v1", aws_region="us-east-1", aws_access_key_id="access key", aws_secret_access_key="secret key", - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) client.api_key = "bearer token" - client.get("/models", cast_to=httpx.Response, options={"follow_redirects": True}) + client.get("/models", cast_to=httpx2.Response, options={"follow_redirects": True}) assert requests[0].headers["Authorization"] == "Bearer bearer token" @@ -528,7 +527,7 @@ def test_explicit_aws_copy_override_wins_over_mutated_api_key() -> None: client = BedrockOpenAI( base_url="https://example.com/openai/v1", api_key="first token", - http_client=sync_http_client(trust_env=False), + http_client=DefaultHttpx2Client(trust_env=False), ) client.api_key = "second token" @@ -545,20 +544,20 @@ def test_explicit_aws_copy_override_wins_over_mutated_api_key() -> None: def test_clearing_legacy_bearer_does_not_switch_to_aws_authentication() -> None: network_calls = 0 - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: nonlocal network_calls network_calls += 1 - return httpx.Response(200, request=request) + return httpx2.Response(200, request=request) with update_env(AWS_ACCESS_KEY_ID="access key", AWS_SECRET_ACCESS_KEY="secret key"): client = BedrockOpenAI( base_url="https://example.com/openai/v1", api_key="bearer token", - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) client.api_key = None # type: ignore[assignment] with pytest.raises(OpenAIError, match="bearer credential must not be empty"): - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) assert network_calls == 0 @@ -570,7 +569,7 @@ def test_legacy_state_repr_does_not_expose_credentials() -> None: aws_access_key_id="secret access key id", aws_secret_access_key="secret access key", aws_session_token="secret session token", - http_client=sync_http_client(trust_env=False), + http_client=DefaultHttpx2Client(trust_env=False), ) assert "secret" not in repr(client._bedrock_state) @@ -578,7 +577,7 @@ def test_legacy_state_repr_does_not_expose_credentials() -> None: bearer_client = BedrockOpenAI( base_url="https://example.com/openai/v1", api_key="secret bearer token", - http_client=sync_http_client(trust_env=False), + http_client=DefaultHttpx2Client(trust_env=False), ) assert "secret bearer token" not in repr(bearer_client._bedrock_runtime_signature) @@ -648,7 +647,7 @@ def test_preserves_aws_credentials_across_with_options() -> None: aws_region="us-east-1", aws_access_key_id="access key", aws_secret_access_key="secret key", - http_client=sync_http_client(trust_env=False), + http_client=DefaultHttpx2Client(trust_env=False), ) copied_client = client.with_options(timeout=1) @@ -912,13 +911,13 @@ def __init__( organization: str | None = None, project: str | None = None, webhook_secret: str | None = None, - base_url: str | httpx.URL | None = None, - websocket_base_url: str | httpx.URL | None = None, + base_url: str | httpx2.URL | None = None, + websocket_base_url: str | httpx2.URL | None = None, timeout: Any = None, max_retries: int = 2, default_headers: Any = None, default_query: Any = None, - http_client: httpx.Client | None = None, + http_client: httpx2.Client | None = None, _enforce_credentials: bool = True, ) -> None: super().__init__( @@ -942,7 +941,7 @@ def __init__( client = LegacyBedrockOpenAI( api_key="token", aws_region="us-east-1", - http_client=sync_http_client(trust_env=False), + http_client=DefaultHttpx2Client(trust_env=False), ) copied_client = client.with_options(timeout=1).with_options(aws_region="us-west-2") @@ -1031,10 +1030,10 @@ def test_rejects_non_bedrock_copy_auth(copy_kwargs: dict[str, Any]) -> None: client.with_options(**copy_kwargs) -@pytest.mark.respx() -def test_passes_non_responses_resources_through(respx_mock: MockRouter) -> None: - respx_mock.post("https://example.com/openai/v1/chat/completions").mock( - return_value=httpx.Response( +@pytest.mark.respx2() +def test_passes_non_responses_resources_through(respx2_mock: MockRouter) -> None: + respx2_mock.post("https://example.com/openai/v1/chat/completions").mock( + return_value=httpx2.Response( 404, json={"error": {"message": "AWS does not support chat completions here"}}, headers={"x-request-id": "req_chat"}, @@ -1046,15 +1045,15 @@ def test_passes_non_responses_resources_through(respx_mock: MockRouter) -> None: client.chat.completions.create(model="gpt-4o", messages=[]) assert exc.value.request_id == "req_chat" - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert calls[0].request.url == URL("https://example.com/openai/v1/chat/completions") @pytest.mark.asyncio -@pytest.mark.respx() -async def test_passes_non_responses_resources_through_async(respx_mock: MockRouter) -> None: - respx_mock.post("https://example.com/openai/v1/chat/completions").mock( - return_value=httpx.Response( +@pytest.mark.respx2() +async def test_passes_non_responses_resources_through_async(respx2_mock: MockRouter) -> None: + respx2_mock.post("https://example.com/openai/v1/chat/completions").mock( + return_value=httpx2.Response( 404, json={"error": {"message": "AWS does not support chat completions here"}}, headers={"x-request-id": "req_chat"}, @@ -1066,14 +1065,14 @@ async def test_passes_non_responses_resources_through_async(respx_mock: MockRout await client.chat.completions.create(model="gpt-4o", messages=[]) assert exc.value.request_id == "req_chat" - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert calls[0].request.url == URL("https://example.com/openai/v1/chat/completions") -@pytest.mark.respx() -def test_passes_responses_features_through(respx_mock: MockRouter) -> None: - respx_mock.post("https://example.com/openai/v1/responses").mock( - return_value=httpx.Response(200, json=RESPONSE_BODY) +@pytest.mark.respx2() +def test_passes_responses_features_through(respx2_mock: MockRouter) -> None: + respx2_mock.post("https://example.com/openai/v1/responses").mock( + return_value=httpx2.Response(200, json=RESPONSE_BODY) ) client = make_sync_client(base_url="https://example.com/openai/v1", api_key="token") @@ -1084,14 +1083,14 @@ def test_passes_responses_features_through(respx_mock: MockRouter) -> None: ) assert response.id == "resp_123" - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert json.loads(calls[0].request.content)["tools"] == [{"type": "web_search_preview"}] -@pytest.mark.respx() -def test_passes_admin_security_routes_through(respx_mock: MockRouter) -> None: - respx_mock.get("https://example.com/openai/v1/organization/invites").mock( - return_value=httpx.Response( +@pytest.mark.respx2() +def test_passes_admin_security_routes_through(respx2_mock: MockRouter) -> None: + respx2_mock.get("https://example.com/openai/v1/organization/invites").mock( + return_value=httpx2.Response( 404, json={"error": {"message": "AWS does not support organization invites here"}}, headers={"x-request-id": "req_admin"}, @@ -1102,16 +1101,16 @@ def test_passes_admin_security_routes_through(respx_mock: MockRouter) -> None: with pytest.raises(NotFoundError, match="AWS does not support organization invites here"): list(client.admin.organization.invites.list()) - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert calls[0].request.headers["Authorization"] == "Bearer token" -@pytest.mark.respx() -def test_refreshes_token_provider_for_admin_security_routes(respx_mock: MockRouter) -> None: - respx_mock.get("https://example.com/openai/v1/organization/invites").mock( +@pytest.mark.respx2() +def test_refreshes_token_provider_for_admin_security_routes(respx2_mock: MockRouter) -> None: + respx2_mock.get("https://example.com/openai/v1/organization/invites").mock( side_effect=[ - httpx.Response(500, json={"error": "server error"}), - httpx.Response( + httpx2.Response(500, json={"error": "server error"}), + httpx2.Response( 404, json={"error": {"message": "AWS does not support organization invites here"}}, headers={"x-request-id": "req_admin"}, @@ -1122,43 +1121,43 @@ def test_refreshes_token_provider_for_admin_security_routes(respx_mock: MockRout client = BedrockOpenAI( base_url="https://example.com/openai/v1", bedrock_token_provider=lambda: next(tokens), - http_client=sync_http_client(trust_env=False), + http_client=DefaultHttpx2Client(trust_env=False), max_retries=1, ) with pytest.raises(NotFoundError, match="AWS does not support organization invites here"): list(client.admin.organization.invites.list()) - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert calls[0].request.headers["Authorization"] == "Bearer first" assert calls[1].request.headers["Authorization"] == "Bearer second" -@pytest.mark.respx() -def test_allows_responses_http_methods(respx_mock: MockRouter) -> None: - respx_mock.post("https://example.com/openai/v1/responses").mock( - return_value=httpx.Response(200, json=RESPONSE_BODY) +@pytest.mark.respx2() +def test_allows_responses_http_methods(respx2_mock: MockRouter) -> None: + respx2_mock.post("https://example.com/openai/v1/responses").mock( + return_value=httpx2.Response(200, json=RESPONSE_BODY) ) - respx_mock.get("https://example.com/openai/v1/responses/resp_123?starting_after=1&stream=true").mock( - return_value=httpx.Response(200, text=response_created_sse(), headers={"Content-Type": "text/event-stream"}) + respx2_mock.get("https://example.com/openai/v1/responses/resp_123?starting_after=1&stream=true").mock( + return_value=httpx2.Response(200, text=response_created_sse(), headers={"Content-Type": "text/event-stream"}) ) - respx_mock.get("https://example.com/openai/v1/responses/resp_123?stream=true").mock( - return_value=httpx.Response(200, text=response_created_sse(), headers={"Content-Type": "text/event-stream"}) + respx2_mock.get("https://example.com/openai/v1/responses/resp_123?stream=true").mock( + return_value=httpx2.Response(200, text=response_created_sse(), headers={"Content-Type": "text/event-stream"}) ) - respx_mock.get("https://example.com/openai/v1/responses/resp_123").mock( - return_value=httpx.Response(200, json=RESPONSE_BODY) + respx2_mock.get("https://example.com/openai/v1/responses/resp_123").mock( + return_value=httpx2.Response(200, json=RESPONSE_BODY) ) - respx_mock.post("https://example.com/openai/v1/responses/resp_123/cancel").mock( - return_value=httpx.Response(200, json=RESPONSE_BODY) + respx2_mock.post("https://example.com/openai/v1/responses/resp_123/cancel").mock( + return_value=httpx2.Response(200, json=RESPONSE_BODY) ) - respx_mock.post("https://example.com/openai/v1/responses/compact").mock( - return_value=httpx.Response(200, json=COMPACTED_RESPONSE_BODY) + respx2_mock.post("https://example.com/openai/v1/responses/compact").mock( + return_value=httpx2.Response(200, json=COMPACTED_RESPONSE_BODY) ) - respx_mock.get("https://example.com/openai/v1/responses/resp_123/input_items").mock( - return_value=httpx.Response(200, json=INPUT_ITEMS_BODY) + respx2_mock.get("https://example.com/openai/v1/responses/resp_123/input_items").mock( + return_value=httpx2.Response(200, json=INPUT_ITEMS_BODY) ) - respx_mock.post("https://example.com/openai/v1/responses/input_tokens").mock( - return_value=httpx.Response(200, json=INPUT_TOKENS_BODY) + respx2_mock.post("https://example.com/openai/v1/responses/input_tokens").mock( + return_value=httpx2.Response(200, json=INPUT_TOKENS_BODY) ) client = make_sync_client(base_url="https://example.com/openai/v1", api_key="token") @@ -1173,17 +1172,17 @@ def test_allows_responses_http_methods(respx_mock: MockRouter) -> None: assert list(client.responses.input_items.list("resp_123")) == [] assert client.responses.input_tokens.count(model="gpt-4o", input="hello").input_tokens == 1 - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert {call.request.headers["Authorization"] for call in calls} == {"Bearer token"} -@pytest.mark.respx() -def test_allows_sse_and_response_wrappers(respx_mock: MockRouter) -> None: - respx_mock.post("https://example.com/openai/v1/responses").mock( +@pytest.mark.respx2() +def test_allows_sse_and_response_wrappers(respx2_mock: MockRouter) -> None: + respx2_mock.post("https://example.com/openai/v1/responses").mock( side_effect=[ - httpx.Response(200, text=response_created_sse(), headers={"Content-Type": "text/event-stream"}), - httpx.Response(200, json=RESPONSE_BODY), - httpx.Response(200, json=RESPONSE_BODY), + httpx2.Response(200, text=response_created_sse(), headers={"Content-Type": "text/event-stream"}), + httpx2.Response(200, json=RESPONSE_BODY), + httpx2.Response(200, json=RESPONSE_BODY), ] ) client = make_sync_client(base_url="https://example.com/openai/v1", api_key="token") diff --git a/tests/lib/test_bedrock_auth_conformance.py b/tests/lib/test_bedrock_auth_conformance.py index 7478b08cbd..08953fa592 100644 --- a/tests/lib/test_bedrock_auth_conformance.py +++ b/tests/lib/test_bedrock_auth_conformance.py @@ -9,7 +9,7 @@ from pathlib import Path from datetime import datetime -import httpx +import httpx2 import pytest import jsonschema @@ -50,7 +50,7 @@ def utcnow(cls) -> datetime: monkeypatch.setattr(botocore_auth.datetime, "datetime", FrozenDatetime) -def _lower_headers(headers: httpx.Headers | dict[str, str]) -> dict[str, str]: +def _lower_headers(headers: httpx2.Headers | dict[str, str]) -> dict[str, str]: return {name.lower(): value for name, value in headers.items()} @@ -59,7 +59,7 @@ def _canonical_request_sha256(case: dict[str, Any], signed_headers: dict[str, st authorization = signed_headers["authorization"] signed_header_names = authorization.split("SignedHeaders=", 1)[1].split(",", 1)[0].split(";") canonical_headers = "".join(f"{name}:{' '.join(signed_headers[name].split())}\n" for name in signed_header_names) - url = httpx.URL(request["url"]) + url = httpx2.URL(request["url"]) canonical_request = "\n".join( ( request["method"], @@ -96,7 +96,7 @@ def test_shared_sigv4_fixture_matches_node(monkeypatch: pytest.MonkeyPatch) -> N url=request["url"], headers={ "content-type": request["contentType"], - "host": httpx.URL(request["url"]).host, + "host": httpx2.URL(request["url"]).host, }, body=body, ) @@ -123,7 +123,7 @@ def test_auth_selection_fixture(case: dict[str, Any], monkeypatch: pytest.Monkey explicit = case["given"]["explicit"] kwargs: dict[str, Any] = { "aws_region": "us-east-1", - "http_client": httpx.Client(trust_env=False), + "http_client": httpx2.Client(trust_env=False), "_enforce_credentials": False, } if "bearer" in explicit: @@ -217,12 +217,12 @@ def credentials_provider() -> _Credentials: _freeze_botocore_time(monkeypatch, timestamps) - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] statuses = iter(case["given"]["response_statuses"]) - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(next(statuses), request=request, json={}) + return httpx2.Response(next(statuses), request=request, json={}) body = base64.b64decode(case["given"]["body_base64"]) with OpenAI( @@ -232,12 +232,12 @@ def handler(request: httpx.Request) -> httpx.Response: credential_provider=credentials_provider, ), max_retries=case["given"].get("max_retries", 1), - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) as client: client.post( "/responses", content=body, - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"headers": {"Content-Type": "application/json"}}, ) @@ -254,7 +254,7 @@ def test_body_replay_fixture(case: dict[str, Any]) -> None: provider_calls = 0 network_calls = 0 body_reads = 0 - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] def credentials_provider() -> _Credentials: nonlocal provider_calls @@ -267,12 +267,12 @@ def body() -> Iterator[bytes]: for chunk in case["given"].get("chunks_base64", []): yield base64.b64decode(chunk) - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: nonlocal network_calls network_calls += 1 requests.append(request) statuses = case["given"].get("response_statuses", [200]) - return httpx.Response(statuses[network_calls - 1], request=request, json={}) + return httpx2.Response(statuses[network_calls - 1], request=request, json={}) body_kind = case["given"]["body_kind"] content: bytes | Iterator[bytes] @@ -289,13 +289,13 @@ def handler(request: httpx.Request) -> httpx.Response: credential_provider=credentials_provider, ), max_retries=case["given"].get("max_retries", 1), - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) as client: if case["expected"]["result"] == "bedrock_non_replayable_body": with pytest.raises(OpenAIError, match="requires a replayable request body"): - client.post("/responses", content=content, cast_to=httpx.Response) + client.post("/responses", content=content, cast_to=httpx2.Response) else: - client.post("/responses", content=content, cast_to=httpx.Response) + client.post("/responses", content=content, cast_to=httpx2.Response) assert network_calls == case["expected"].get("network_attempts", case["expected"].get("attempts")) if body_kind == "bytes": @@ -321,10 +321,10 @@ async def body() -> AsyncIterator[bytes]: body_reads += 1 yield b"body" - async def handler(request: httpx.Request) -> httpx.Response: + async def handler(request: httpx2.Request) -> httpx2.Response: nonlocal network_calls network_calls += 1 - return httpx.Response(200, request=request) + return httpx2.Response(200, request=request) async with AsyncOpenAI( provider=bedrock( @@ -332,10 +332,10 @@ async def handler(request: httpx.Request) -> httpx.Response: region="us-east-1", credential_provider=credentials_provider, ), - http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.AsyncClient(transport=httpx2.MockTransport(handler), trust_env=False), ) as client: with pytest.raises(OpenAIError, match="requires a replayable request body"): - await client.post("/responses", content=body(), cast_to=httpx.Response) + await client.post("/responses", content=body(), cast_to=httpx2.Response) assert (body_reads, provider_calls, network_calls) == (0, 0, 0) @@ -349,8 +349,8 @@ def credentials_provider() -> _Credentials: provider_threads.append(threading.get_ident()) return _Credentials("access-key", "secret-key") - async def handler(request: httpx.Request) -> httpx.Response: - return httpx.Response(200, request=request, json={}) + async def handler(request: httpx2.Request) -> httpx2.Response: + return httpx2.Response(200, request=request, json={}) async with AsyncOpenAI( provider=bedrock( @@ -358,20 +358,20 @@ async def handler(request: httpx.Request) -> httpx.Response: region="us-east-1", credential_provider=credentials_provider, ), - http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.AsyncClient(transport=httpx2.MockTransport(handler), trust_env=False), ) as client: - await client.post("/responses", content=b"{}", cast_to=httpx.Response) + await client.post("/responses", content=b"{}", cast_to=httpx2.Response) assert provider_threads assert all(thread_id != event_loop_thread for thread_id in provider_threads) def test_custom_http_client_auth_cannot_replace_sigv4() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) with OpenAI( provider=bedrock( @@ -380,25 +380,25 @@ def handler(request: httpx.Request) -> httpx.Response: access_key_id="access-key", secret_access_key="secret-key", ), - http_client=httpx.Client( - auth=httpx.BasicAuth("username", "password"), - transport=httpx.MockTransport(handler), + http_client=httpx2.Client( + auth=httpx2.BasicAuth("username", "password"), + transport=httpx2.MockTransport(handler), trust_env=False, ), ) as client: - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) assert requests[0].headers["Authorization"].startswith("AWS4-HMAC-SHA256 Credential=access-key/") def test_sigv4_redirects_are_not_followed() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) if len(requests) == 1: - return httpx.Response(307, request=request, headers={"Location": "/redirected"}) - return httpx.Response(200, request=request) + return httpx2.Response(307, request=request, headers={"Location": "/redirected"}) + return httpx2.Response(200, request=request) with OpenAI( provider=bedrock( @@ -407,10 +407,10 @@ def handler(request: httpx.Request) -> httpx.Response: access_key_id="access-key", secret_access_key="secret-key", ), - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) as client: with pytest.raises(APIStatusError) as exc: - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) assert exc.value.status_code == 307 assert len(requests) == 1 diff --git a/tests/lib/test_bedrock_credential_chain.py b/tests/lib/test_bedrock_credential_chain.py index 93e476fe5b..fec363aeb1 100644 --- a/tests/lib/test_bedrock_credential_chain.py +++ b/tests/lib/test_bedrock_credential_chain.py @@ -10,7 +10,7 @@ from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler from typing_extensions import override -import httpx +import httpx2 import pytest from openai import OpenAI @@ -69,18 +69,18 @@ def _isolate_aws_environment(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> return config_path, credentials_path -def _signed_request(**provider_options: Any) -> httpx.Request: - requests: list[httpx.Request] = [] +def _signed_request(**provider_options: Any) -> httpx2.Request: + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) with OpenAI( provider=bedrock(**provider_options), - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) as client: - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) assert len(requests) == 1 return requests[0] @@ -95,7 +95,7 @@ def _credentials_metadata(name: str, *, expiration: str = _FUTURE_EXPIRATION) -> } -def _assert_signed_with(request: httpx.Request, name: str, *, region: str) -> None: +def _assert_signed_with(request: httpx2.Request, name: str, *, region: str) -> None: assert request.url.host == f"bedrock-mantle.{region}.api.aws" assert f"Credential={name}-access-key/" in request.headers["Authorization"] assert request.headers["X-Amz-Security-Token"] == f"{name}-session-token" @@ -370,18 +370,18 @@ def get_credentials(_: object) -> Any: return credentials monkeypatch.setattr(session_module.Session, "get_credentials", get_credentials) - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(500 if len(requests) == 1 else 200, request=request, json={}) + return httpx2.Response(500 if len(requests) == 1 else 200, request=request, json={}) with OpenAI( provider=bedrock(), max_retries=1, - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) as client: - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) assert refreshes == 2 assert len(requests) == 2 diff --git a/tests/lib/test_bedrock_provider.py b/tests/lib/test_bedrock_provider.py index 236ce21359..7fd1ab971a 100644 --- a/tests/lib/test_bedrock_provider.py +++ b/tests/lib/test_bedrock_provider.py @@ -3,7 +3,7 @@ import builtins from typing import Any, Iterator, cast -import httpx +import httpx2 import pytest import openai.lib._bedrock_auth as bedrock_auth_module @@ -15,47 +15,47 @@ def test_sync_provider_owns_endpoint_and_bearer_authentication() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) client = OpenAI( provider=bedrock(region="us-east-1", api_key="bedrock token"), - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) - assert client.base_url == httpx.URL("https://bedrock-mantle.us-east-1.api.aws/openai/v1/") - assert requests[0].url == httpx.URL("https://bedrock-mantle.us-east-1.api.aws/openai/v1/models") + assert client.base_url == httpx2.URL("https://bedrock-mantle.us-east-1.api.aws/openai/v1/") + assert requests[0].url == httpx2.URL("https://bedrock-mantle.us-east-1.api.aws/openai/v1/models") assert requests[0].headers["Authorization"] == "Bearer bedrock token" @pytest.mark.asyncio async def test_async_provider_owns_endpoint_and_bearer_authentication() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - async def handler(request: httpx.Request) -> httpx.Response: + async def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) client = AsyncOpenAI( provider=bedrock(region="us-east-1", token_provider=lambda: "bedrock token"), - http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.AsyncClient(transport=httpx2.MockTransport(handler), trust_env=False), ) - await client.get("/models", cast_to=httpx.Response) + await client.get("/models", cast_to=httpx2.Response) await client.close() assert requests[0].headers["Authorization"] == "Bearer bedrock token" def test_provider_ignores_openai_environment_configuration() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) with update_env( OPENAI_API_KEY="openai token", @@ -64,9 +64,9 @@ def handler(request: httpx.Request) -> httpx.Response: ): client = OpenAI( provider=bedrock(region="us-east-1", api_key="bedrock token"), - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) assert client.api_key == "" assert requests[0].url.host == "bedrock-mantle.us-east-1.api.aws" @@ -98,16 +98,16 @@ def test_provider_survives_with_options_and_can_be_replaced() -> None: assert copied.base_url == client.base_url assert copied._provider is client._provider - assert replaced.base_url == httpx.URL("https://bedrock-mantle.eu-west-1.api.aws/openai/v1/") + assert replaced.base_url == httpx2.URL("https://bedrock-mantle.eu-west-1.api.aws/openai/v1/") assert replaced._provider is not client._provider def test_switching_to_provider_drops_inherited_openai_metadata() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) with update_env( OPENAI_CUSTOM_HEADERS="X-OpenAI-Ambient: leak", @@ -117,11 +117,11 @@ def handler(request: httpx.Request) -> httpx.Response: client = OpenAI( api_key="openai token", default_headers={"X-OpenAI-Custom": "leak"}, - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) provider_client = client.with_options(provider=bedrock(region="us-east-1", api_key="bedrock token")) - provider_client.get("/models", cast_to=httpx.Response) + provider_client.get("/models", cast_to=httpx2.Response) headers = requests[0].headers assert headers["Authorization"] == "Bearer bedrock token" @@ -133,22 +133,22 @@ def handler(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio async def test_async_switching_to_provider_drops_inherited_openai_metadata() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - async def handler(request: httpx.Request) -> httpx.Response: + async def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) client = AsyncOpenAI( api_key="openai token", organization="openai-org", project="openai-project", default_headers={"X-OpenAI-Custom": "leak"}, - http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.AsyncClient(transport=httpx2.MockTransport(handler), trust_env=False), ) provider_client = client.with_options(provider=bedrock(region="us-east-1", api_key="bedrock token")) - await provider_client.get("/models", cast_to=httpx.Response) + await provider_client.get("/models", cast_to=httpx2.Response) await provider_client.close() headers = requests[0].headers @@ -159,11 +159,11 @@ async def handler(request: httpx.Request) -> httpx.Response: def test_provider_metadata_survives_same_provider_clone_but_not_replacement() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) first_provider = bedrock(region="us-east-1", api_key="first token") client = OpenAI( @@ -171,12 +171,12 @@ def handler(request: httpx.Request) -> httpx.Response: organization="provider-org", project="provider-project", default_headers={"X-Provider-Custom": "preserve-me"}, - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) - client.with_options(timeout=1).get("/models", cast_to=httpx.Response) + client.with_options(timeout=1).get("/models", cast_to=httpx2.Response) client.with_options(provider=bedrock(region="us-east-1", api_key="second token")).get( - "/models", cast_to=httpx.Response + "/models", cast_to=httpx2.Response ) same_provider_headers, replacement_headers = (request.headers for request in requests) @@ -193,8 +193,8 @@ class NormalizingProvider: name = "normalizing" def configure(self) -> _ProviderRuntime: - def normalize(response: httpx.Response) -> httpx.Response: - return httpx.Response(200, request=response.request, json={"normalized": True}) + def normalize(response: httpx2.Response) -> httpx2.Response: + return httpx2.Response(200, request=response.request, json={"normalized": True}) return _ProviderRuntime( name=self.name, @@ -205,35 +205,35 @@ def normalize(response: httpx.Response) -> httpx.Response: client = OpenAI( provider=_create_provider(NormalizingProvider()), max_retries=0, - http_client=httpx.Client( - transport=httpx.MockTransport(lambda request: httpx.Response(500, request=request, json={})), + http_client=httpx2.Client( + transport=httpx2.MockTransport(lambda request: httpx2.Response(500, request=request, json={})), trust_env=False, ), ) - response = client.get("/models", cast_to=httpx.Response) + response = client.get("/models", cast_to=httpx2.Response) assert response.status_code == 200 assert response.json() == {"normalized": True} def test_environment_bearer_mode_survives_clone_and_refreshes_each_attempt() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) with update_env(AWS_BEARER_TOKEN_BEDROCK="first token"): client = OpenAI( provider=bedrock(region="us-east-1"), - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) copied = client.with_options(timeout=1) with update_env(AWS_BEARER_TOKEN_BEDROCK="second token"): - copied.get("/models", cast_to=httpx.Response) + copied.get("/models", cast_to=httpx2.Response) assert [request.headers["Authorization"] for request in requests] == ["Bearer first token", "Bearer second token"] @@ -251,7 +251,7 @@ def test_provider_can_be_removed_with_explicit_openai_credentials() -> None: assert copied._provider is None assert copied.api_key == "openai token" - assert copied.base_url == httpx.URL("https://api.openai.com/v1/") + assert copied.base_url == httpx2.URL("https://api.openai.com/v1/") assert copied.organization is None assert copied.project is None assert "X-Provider-Custom" not in copied.default_headers @@ -285,29 +285,29 @@ def import_module( raise ImportError(name) return real_import(name, globals, locals, fromlist, level) - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: nonlocal network_calls network_calls += 1 - return httpx.Response(200, request=request) + return httpx2.Response(200, request=request) monkeypatch.setattr(builtins, "__import__", import_module) client = OpenAI( provider=bedrock(region="us-east-1"), - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) with pytest.raises(OpenAIError, match=r"pip install openai\[bedrock\]"): - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) assert network_calls == 0 def test_api_key_none_skips_environment_bearer_fallback() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) with update_env( AWS_BEARER_TOKEN_BEDROCK="environment bearer", @@ -316,9 +316,9 @@ def handler(request: httpx.Request) -> httpx.Response: ): client = OpenAI( provider=bedrock(region="us-east-1", api_key=None), - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) assert requests[0].headers["Authorization"].startswith("AWS4-HMAC-SHA256 Credential=access key/") @@ -326,20 +326,20 @@ def handler(request: httpx.Request) -> httpx.Response: def test_provider_rejects_custom_authorization_before_network() -> None: network_calls = 0 - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: nonlocal network_calls network_calls += 1 - return httpx.Response(200, request=request) + return httpx2.Response(200, request=request) client = OpenAI( provider=bedrock(region="us-east-1", api_key="bedrock token"), - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) with pytest.raises(OpenAIError, match="cannot be combined with a custom `Authorization` header"): client.get( "/models", - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"headers": {"Authorization": "Bearer custom"}}, ) @@ -355,18 +355,18 @@ def token_provider() -> str: provider_calls += 1 return "bedrock token" - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: nonlocal network_calls network_calls += 1 - return httpx.Response(200, request=request) + return httpx2.Response(200, request=request) client = OpenAI( provider=bedrock(base_url="https://bedrock.example/openai/v1", token_provider=token_provider), - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) with pytest.raises(OpenAIError, match="origin other than the configured provider URL"): - client.get("https://attacker.example/steal", cast_to=httpx.Response) + client.get("https://attacker.example/steal", cast_to=httpx2.Response) assert (provider_calls, network_calls) == (0, 0) @@ -381,40 +381,40 @@ async def token_provider() -> str: provider_calls += 1 return "bedrock token" - async def handler(request: httpx.Request) -> httpx.Response: + async def handler(request: httpx2.Request) -> httpx2.Response: nonlocal network_calls network_calls += 1 - return httpx.Response(200, request=request) + return httpx2.Response(200, request=request) client = AsyncOpenAI( provider=bedrock(base_url="https://bedrock.example/openai/v1", token_provider=token_provider), - http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.AsyncClient(transport=httpx2.MockTransport(handler), trust_env=False), ) with pytest.raises(OpenAIError, match="origin other than the configured provider URL"): - await client.get("https://attacker.example/steal", cast_to=httpx.Response) + await client.get("https://attacker.example/steal", cast_to=httpx2.Response) await client.close() assert (provider_calls, network_calls) == (0, 0) def test_bearer_provider_allows_one_shot_body_when_retries_are_disabled() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] def body() -> Iterator[bytes]: yield b"body" - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request) + return httpx2.Response(200, request=request) client = OpenAI( provider=bedrock(base_url="https://bedrock.example/openai/v1", api_key="bedrock token"), max_retries=0, - http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + http_client=httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False), ) - client.post("/responses", content=body(), cast_to=httpx.Response) + client.post("/responses", content=body(), cast_to=httpx2.Response) assert requests[0].content == b"body" diff --git a/tests/respx2/FORK.md b/tests/respx2/FORK.md new file mode 100644 index 0000000000..18b9cb24d8 --- /dev/null +++ b/tests/respx2/FORK.md @@ -0,0 +1,13 @@ +# HTTPX2-native RESPX fork + +This directory contains an SDK test-only fork of RESPX 0.23.1. + +- Upstream: https://github.com/lundberg/respx +- Upstream tag: `0.23.1` +- Upstream commit: `fc8b43bc74a69d07a6bdccf61522069b12bb8fad` +- License: BSD 3-Clause; the original license is preserved in `LICENSE.md`. +- The upstream README is preserved without modification in `README.md`. + +The fork replaces HTTPX and HTTPCORE imports and interception targets with HTTPX2 and HTTPCORE2, exposes the `respx2` pytest marker and `respx2_mock` fixture, and changes package imports to `tests.respx2`. This lets the SDK retain RESPX request matching, response side effects, decorators, and call history without installing legacy HTTPX in its normal test environment. + +The fork is limited to the SDK test suite and may be removed or upstreamed once RESPX supports HTTPX2 without requiring legacy HTTPX. diff --git a/tests/respx2/LICENSE.md b/tests/respx2/LICENSE.md new file mode 100644 index 0000000000..07b3b0b2b5 --- /dev/null +++ b/tests/respx2/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2019, 5 Monkeys Agency AB +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/respx2/README.md b/tests/respx2/README.md new file mode 100644 index 0000000000..392ac41a86 --- /dev/null +++ b/tests/respx2/README.md @@ -0,0 +1,84 @@ +

+ RESPX +

+

+ RESPX - Mock HTTPX with awesome request patterns and response side effects. +

+ +--- + +[![tests](https://img.shields.io/github/actions/workflow/status/lundberg/respx/test.yml?branch=master&label=tests&logo=github&logoColor=white&style=for-the-badge)](https://github.com/lundberg/respx/actions/workflows/test.yml) +[![codecov](https://img.shields.io/codecov/c/github/lundberg/respx?logo=codecov&logoColor=white&style=for-the-badge)](https://codecov.io/gh/lundberg/respx) +[![PyPi Version](https://img.shields.io/pypi/v/respx?logo=pypi&logoColor=white&style=for-the-badge)](https://pypi.org/project/respx/) +[![PyPI Downloads](https://img.shields.io/pypi/dm/respx?style=for-the-badge&logo=pypi&logoColor=white&color=mediumpurple)](https://pypi.org/project/respx/) +[![Python Versions](https://img.shields.io/pypi/pyversions/respx?logo=python&logoColor=white&style=for-the-badge)](https://pypi.org/project/respx/) + +## Documentation + +Full documentation is available at +[lundberg.github.io/respx](https://lundberg.github.io/respx/) + +## QuickStart + +RESPX is a simple, _yet powerful_, utility for mocking out the +[HTTPX](https://www.python-httpx.org/), _and +[HTTP Core](https://www.encode.io/httpcore/)_, libraries. + +Start by [patching](https://lundberg.github.io/respx/guide/#mock-httpx) `HTTPX`, using +`respx.mock`, then add request +[routes](https://lundberg.github.io/respx/guide/#routing-requests) to mock +[responses](https://lundberg.github.io/respx/guide/#mocking-responses). + +```python +import httpx +import respx + +from httpx import Response + + +@respx.mock +def test_example(): + my_route = respx.get("https://example.org/").mock(return_value=Response(204)) + response = httpx.get("https://example.org/") + assert my_route.called + assert response.status_code == 204 +``` + +> Read the [User Guide](https://lundberg.github.io/respx/guide/) for a complete +> walk-through. + +### pytest + httpx + +For a neater `pytest` experience, RESPX includes a `respx_mock` _fixture_ for easy +`HTTPX` mocking, along with an optional `respx` _marker_ to fine-tune the mock +[settings](https://lundberg.github.io/respx/api/#configuration). + +```python +import httpx +import pytest + + +def test_default(respx_mock): + respx_mock.get("https://foo.bar/").mock(return_value=httpx.Response(204)) + response = httpx.get("https://foo.bar/") + assert response.status_code == 204 + + +@pytest.mark.respx(base_url="https://foo.bar") +def test_with_marker(respx_mock): + respx_mock.get("/baz/").mock(return_value=httpx.Response(204)) + response = httpx.get("https://foo.bar/baz/") + assert response.status_code == 204 +``` + +## Installation + +Install with pip: + +```console +$ pip install respx +``` + +Requires Python 3.8+ and HTTPX 0.25+. See +[Changelog](https://github.com/lundberg/respx/blob/master/CHANGELOG.md) for older HTTPX +compatibility. diff --git a/tests/respx2/__init__.py b/tests/respx2/__init__.py new file mode 100644 index 0000000000..13694fdc82 --- /dev/null +++ b/tests/respx2/__init__.py @@ -0,0 +1,56 @@ +from .__version__ import __version__ +from .handlers import ASGIHandler, WSGIHandler +from .models import MockResponse, Route +from .router import MockRouter, Router +from .utils import SetCookie + +from .api import ( # isort:skip + mock, + routes, + calls, + start, + stop, + clear, + reset, + pop, + route, + add, + request, + get, + post, + put, + patch, + delete, + head, + options, +) + + +__all__ = [ + "__version__", + "MockResponse", + "MockRouter", + "ASGIHandler", + "WSGIHandler", + "Router", + "Route", + "SetCookie", + "mock", + "routes", + "calls", + "start", + "stop", + "clear", + "reset", + "pop", + "route", + "add", + "request", + "get", + "post", + "put", + "patch", + "delete", + "head", + "options", +] diff --git a/tests/respx2/__version__.py b/tests/respx2/__version__.py new file mode 100644 index 0000000000..43e16f5d29 --- /dev/null +++ b/tests/respx2/__version__.py @@ -0,0 +1 @@ +__version__ = "0.23.1" diff --git a/tests/respx2/api.py b/tests/respx2/api.py new file mode 100644 index 0000000000..89e123c60b --- /dev/null +++ b/tests/respx2/api.py @@ -0,0 +1,116 @@ +from typing import Any, Optional, Union, overload + +from .models import CallList, Route +from .patterns import Pattern +from .router import MockRouter +from .types import DefaultType, URLPatternTypes + +mock = MockRouter(assert_all_called=False) + +routes = mock.routes +calls: CallList = mock.calls + + +def start() -> None: + global mock + mock.start() + + +def stop(clear: bool = True, reset: bool = True) -> None: + global mock + mock.stop(clear=clear, reset=reset) + + +def clear() -> None: + global mock + mock.clear() + + +def reset() -> None: + global mock + mock.reset() + + +@overload +def pop(name: str) -> Route: + ... # pragma: nocover + + +@overload +def pop(name: str, default: DefaultType) -> Union[Route, DefaultType]: + ... # pragma: nocover + + +def pop(name, default=...): + global mock + return mock.pop(name, default=default) + + +def route(*patterns: Pattern, name: Optional[str] = None, **lookups: Any) -> Route: + global mock + return mock.route(*patterns, name=name, **lookups) + + +def add(route: Route, *, name: Optional[str] = None) -> Route: + global mock + return mock.add(route, name=name) + + +def request( + method: str, + url: Optional[URLPatternTypes] = None, + *, + name: Optional[str] = None, + **lookups: Any, +) -> Route: + global mock + return mock.request(method, url, name=name, **lookups) + + +def get( + url: Optional[URLPatternTypes] = None, *, name: Optional[str] = None, **lookups: Any +) -> Route: + global mock + return mock.get(url, name=name, **lookups) + + +def post( + url: Optional[URLPatternTypes] = None, *, name: Optional[str] = None, **lookups: Any +) -> Route: + global mock + return mock.post(url, name=name, **lookups) + + +def put( + url: Optional[URLPatternTypes] = None, *, name: Optional[str] = None, **lookups: Any +) -> Route: + global mock + return mock.put(url, name=name, **lookups) + + +def patch( + url: Optional[URLPatternTypes] = None, *, name: Optional[str] = None, **lookups: Any +) -> Route: + global mock + return mock.patch(url, name=name, **lookups) + + +def delete( + url: Optional[URLPatternTypes] = None, *, name: Optional[str] = None, **lookups: Any +) -> Route: + global mock + return mock.delete(url, name=name, **lookups) + + +def head( + url: Optional[URLPatternTypes] = None, *, name: Optional[str] = None, **lookups: Any +) -> Route: + global mock + return mock.head(url, name=name, **lookups) + + +def options( + url: Optional[URLPatternTypes] = None, *, name: Optional[str] = None, **lookups: Any +) -> Route: + global mock + return mock.options(url, name=name, **lookups) diff --git a/tests/respx2/fixtures.py b/tests/respx2/fixtures.py new file mode 100644 index 0000000000..1513fbf74f --- /dev/null +++ b/tests/respx2/fixtures.py @@ -0,0 +1,12 @@ +try: + import pytest +except ImportError: # pragma: nocover + pass +else: + import asyncio + + @pytest.fixture(scope="session") + def session_event_loop(): + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() diff --git a/tests/respx2/handlers.py b/tests/respx2/handlers.py new file mode 100644 index 0000000000..b3a3cd7dda --- /dev/null +++ b/tests/respx2/handlers.py @@ -0,0 +1,41 @@ +from typing import Any, Callable + +import httpx2 as httpx + + +class TransportHandler: + def __init__(self, transport: httpx.BaseTransport) -> None: + self.transport = transport + + def __call__(self, request: httpx.Request) -> httpx.Response: + if not isinstance( + request.stream, + httpx.SyncByteStream, + ): # pragma: nocover + raise RuntimeError("Attempted to route an async request to a sync app.") + + return self.transport.handle_request(request) + + +class AsyncTransportHandler: + def __init__(self, transport: httpx.AsyncBaseTransport) -> None: + self.transport = transport + + async def __call__(self, request: httpx.Request) -> httpx.Response: + if not isinstance( + request.stream, + httpx.AsyncByteStream, + ): # pragma: nocover + raise RuntimeError("Attempted to route a sync request to an async app.") + + return await self.transport.handle_async_request(request) + + +class WSGIHandler(TransportHandler): + def __init__(self, app: Callable, **kwargs: Any) -> None: + super().__init__(httpx.WSGITransport(app=app, **kwargs)) + + +class ASGIHandler(AsyncTransportHandler): + def __init__(self, app: Callable, **kwargs: Any) -> None: + super().__init__(httpx.ASGITransport(app=app, **kwargs)) diff --git a/tests/respx2/mocks.py b/tests/respx2/mocks.py new file mode 100644 index 0000000000..5696ee95c6 --- /dev/null +++ b/tests/respx2/mocks.py @@ -0,0 +1,339 @@ +import inspect +from abc import ABC +from types import MappingProxyType +from typing import TYPE_CHECKING, ClassVar, Dict, List, Type +from unittest import mock + +import httpcore2 as httpcore +import httpx2 as httpx + +from tests.respx2.patterns import parse_url + +from .models import AllMockedAssertionError, PassThrough +from .transports import TryTransport + +if TYPE_CHECKING: + from .router import Router # pragma: nocover + +__all__ = ["Mocker", "HTTPCoreMocker"] + + +class Mocker(ABC): + _patches: ClassVar[List[mock._patch]] + name: ClassVar[str] + routers: ClassVar[List["Router"]] + targets: ClassVar[List[str]] + target_methods: ClassVar[List[str]] + + # Automatically register all the subclasses in this dict + __registry: ClassVar[Dict[str, Type["Mocker"]]] = {} + registry = MappingProxyType(__registry) + + def __init_subclass__(cls) -> None: + if not getattr(cls, "name", None) or ABC in cls.__bases__: + return + + if cls.name in cls.__registry: + raise TypeError( + "Subclasses of Mocker must define a unique name. " + f"{cls.name!r} is already defined as {cls.__registry[cls.name]!r}" + ) + + cls.routers = [] + cls._patches = [] + cls.__registry[cls.name] = cls + + @classmethod + def register(cls, router: "Router") -> None: + cls.routers.append(router) + + @classmethod + def unregister(cls, router: "Router") -> bool: + if router in cls.routers: + cls.routers.remove(router) + return True + return False + + @classmethod + def add_targets(cls, *targets: str) -> None: + targets = tuple(filter(lambda t: t not in cls.targets, targets)) + if targets: + cls.targets.extend(targets) + cls.restart() + + @classmethod + def remove_targets(cls, *targets: str) -> None: + targets = tuple(filter(lambda t: t in cls.targets, targets)) + if targets: + for target in targets: + cls.targets.remove(target) + cls.restart() + + @classmethod + def start(cls) -> None: + # Ensure we only patch once! + if cls._patches: + return + + # Start patching target transports + for target in cls.targets: + for method in cls.target_methods: + try: + spec = f"{target}.{method}" + patch = mock.patch(spec, spec=True, new_callable=cls.mock) + patch.start() + cls._patches.append(patch) + except AttributeError: + pass + + @classmethod + def stop(cls, force: bool = False) -> None: + # Ensure we don't stop patching when registered transports exists + if cls.routers and not force: + return + + # Stop patching HTTPX + while cls._patches: + patch = cls._patches.pop() + patch.stop() + + @classmethod + def restart(cls) -> None: + # Only stop and start if started + if cls._patches: # pragma: nocover + cls.stop(force=True) + cls.start() + + @classmethod + def handler(cls, httpx_request): + httpx_response = None + assertion_error = None + for router in cls.routers: + try: + httpx_response = router.handler(httpx_request) + except AllMockedAssertionError as error: + assertion_error = error + continue + else: + break + if assertion_error and not httpx_response: + raise assertion_error + return httpx_response + + @classmethod + async def async_handler(cls, httpx_request): + httpx_response = None + assertion_error = None + for router in cls.routers: + try: + httpx_response = await router.async_handler(httpx_request) + except AllMockedAssertionError as error: + assertion_error = error + continue + else: + break + if assertion_error and not httpx_response: + raise assertion_error + return httpx_response + + @classmethod + def mock(cls, spec): + raise NotImplementedError() # pragma: nocover + + +class HTTPXMocker(Mocker): + name = "httpx" + targets = [ + "httpx2._client.Client", + "httpx2._client.AsyncClient", + ] + target_methods = ["_transport_for_url"] + + @classmethod + def mock(cls, spec): + def _transport_for_url(self, *args, **kwargs): + handler = ( + cls.async_handler + if inspect.iscoroutinefunction(self.request) + else cls.handler + ) + mock_transport = httpx.MockTransport(handler) + pass_through_transport = spec(self, *args, **kwargs) + transport = TryTransport([mock_transport, pass_through_transport]) + return transport + + return _transport_for_url + + +class AbstractRequestMocker(Mocker): + @classmethod + def mock(cls, spec): + if spec.__name__ not in cls.target_methods: + # Prevent mocking mock + return spec + + argspec = inspect.getfullargspec(spec) + + def mock(self, *args, **kwargs): + kwargs = cls._merge_args_and_kwargs(argspec, args, kwargs) + request = cls.to_httpx_request(**kwargs) + request, kwargs = cls.prepare_sync_request(request, **kwargs) + response = cls._send_sync_request( + request, target_spec=spec, instance=self, **kwargs + ) + return response + + async def amock(self, *args, **kwargs): + kwargs = cls._merge_args_and_kwargs(argspec, args, kwargs) + request = cls.to_httpx_request(**kwargs) + request, kwargs = await cls.prepare_async_request(request, **kwargs) + response = await cls._send_async_request( + request, target_spec=spec, instance=self, **kwargs + ) + return response + + return amock if inspect.iscoroutinefunction(spec) else mock + + @classmethod + def _merge_args_and_kwargs(cls, argspec, args, kwargs): + arg_names = argspec.args[1:] # Omit self + new_kwargs = ( + dict(zip(arg_names[-len(argspec.defaults) :], argspec.defaults)) + if argspec.defaults + else dict() + ) + new_kwargs.update(zip(arg_names, args)) + new_kwargs.update(kwargs) + return new_kwargs + + @classmethod + def _send_sync_request(cls, httpx_request, *, target_spec, instance, **kwargs): + try: + httpx_response = cls.handler(httpx_request) + except PassThrough: + response = target_spec(instance, **kwargs) + else: + response = cls.from_sync_httpx_response(httpx_response, instance, **kwargs) + return response + + @classmethod + async def _send_async_request( + cls, httpx_request, *, target_spec, instance, **kwargs + ): + try: + httpx_response = await cls.async_handler(httpx_request) + except PassThrough: + response = await target_spec(instance, **kwargs) + else: + response = await cls.from_async_httpx_response( + httpx_response, instance, **kwargs + ) + return response + + @classmethod + def prepare_sync_request(cls, httpx_request, **kwargs): + """ + Sync pre-read request body + """ + httpx_request.read() + return httpx_request, kwargs + + @classmethod + async def prepare_async_request(cls, httpx_request, **kwargs): + """ + Async pre-read request body + """ + await httpx_request.aread() + return httpx_request, kwargs + + @classmethod + def to_httpx_request(cls, **kwargs): + raise NotImplementedError() # pragma: nocover + + @classmethod + def from_sync_httpx_response(cls, httpx_response, target, **kwargs): + raise NotImplementedError() # pragma: nocover + + @classmethod + async def from_async_httpx_response(cls, httpx_response, target, **kwargs): + raise NotImplementedError() # pragma: nocover + + +class HTTPCoreMocker(AbstractRequestMocker): + name = "httpcore" + targets = [ + "httpcore2._sync.connection.HTTPConnection", + "httpcore2._sync.connection_pool.ConnectionPool", + "httpcore2._sync.http_proxy.HTTPProxy", + "httpcore2._async.connection.AsyncHTTPConnection", + "httpcore2._async.connection_pool.AsyncConnectionPool", + "httpcore2._async.http_proxy.AsyncHTTPProxy", + ] + target_methods = ["handle_request", "handle_async_request"] + + @classmethod + def prepare_sync_request(cls, httpx_request, **kwargs): + """ + Sync pre-read request body, and update transport request arg. + """ + httpx_request, kwargs = super().prepare_sync_request(httpx_request, **kwargs) + kwargs["request"].stream = httpx_request.stream + return httpx_request, kwargs + + @classmethod + async def prepare_async_request(cls, httpx_request, **kwargs): + """ + Async pre-read request body, and update transport request arg. + """ + httpx_request, kwargs = await super().prepare_async_request( + httpx_request, **kwargs + ) + kwargs["request"].stream = httpx_request.stream + return httpx_request, kwargs + + @classmethod + def to_httpx_request(cls, **kwargs): + """ + Create a `HTTPX` request from transport request arg. + """ + request = kwargs["request"] + method = ( + request.method.decode("ascii") + if isinstance(request.method, bytes) + else request.method + ) + raw_url = ( + request.url.scheme, + request.url.host, + request.url.port, + request.url.target, + ) + return httpx.Request( + method, + parse_url(raw_url), + headers=request.headers, + stream=request.stream, + extensions=request.extensions, + ) + + @classmethod + def from_sync_httpx_response(cls, httpx_response, target, **kwargs): + """ + Create a `httpcore` response from a `HTTPX` response. + """ + return httpcore.Response( + status=httpx_response.status_code, + headers=httpx_response.headers.raw, + content=httpx_response.stream, + extensions=httpx_response.extensions, + ) + + @classmethod + async def from_async_httpx_response(cls, httpx_response, target, **kwargs): + """ + Create a `httpcore` response from a `HTTPX` response. + """ + return cls.from_sync_httpx_response(httpx_response, target, **kwargs) + + +DEFAULT_MOCKER: str = HTTPCoreMocker.name diff --git a/tests/respx2/models.py b/tests/respx2/models.py new file mode 100644 index 0000000000..eb42b205af --- /dev/null +++ b/tests/respx2/models.py @@ -0,0 +1,550 @@ +import inspect +from typing import ( + Any, + Dict, + Iterator, + List, + NamedTuple, + Optional, + Sequence, + Tuple, + Type, + Union, +) +from unittest import mock +from warnings import warn + +import httpx2 as httpx + +from tests.respx2.utils import SetCookie + +from .patterns import M, Pattern +from .types import ( + CallableSideEffect, + Content, + CookieTypes, + HeaderTypes, + ResolvedResponseTypes, + RouteResultTypes, + SideEffectListTypes, + SideEffectTypes, +) + + +def clone_response(response: httpx.Response, request: httpx.Request) -> httpx.Response: + """ + Clones a httpx Response for given request. + """ + response = httpx.Response( + response.status_code, + headers=response.headers, + stream=response.stream, + request=request, + extensions=dict(response.extensions), + ) + return response + + +class Call(NamedTuple): + request: httpx.Request + optional_response: Optional[httpx.Response] + + @property + def response(self) -> httpx.Response: + if self.optional_response is None: + raise ValueError(f"{self!r} has no response") + return self.optional_response + + @property + def has_response(self) -> bool: + return self.optional_response is not None + + +class CallList(list, mock.NonCallableMock): + def __init__(self, *args: Sequence[Call], name: Any = "respx") -> None: + super().__init__(*args) + mock.NonCallableMock.__init__(self, name=name) + + @property + def called(self) -> bool: # type: ignore[override] + return bool(self) + + @property + def call_count(self) -> int: # type: ignore[override] + return len(self) + + @property + def last(self) -> Call: + return self[-1] + + def record( + self, request: httpx.Request, response: Optional[httpx.Response] + ) -> Call: + call = Call(request=request, optional_response=response) + self.append(call) + return call + + +class MockResponse(httpx.Response): + def __init__( + self, + status_code: Optional[int] = None, + *, + content: Optional[Content] = None, + content_type: Optional[str] = None, + http_version: Optional[str] = None, + cookies: Optional[Union[CookieTypes, Sequence[SetCookie]]] = None, + **kwargs: Any, + ) -> None: + if not isinstance(content, (str, bytes)) and ( + callable(content) or isinstance(content, (dict, Exception)) + ): + raise TypeError( + f"MockResponse content can only be str, bytes or byte stream" + f"got {content!r}. Please use json=... or side effects." + ) + + if content is not None: + kwargs["content"] = content + if http_version: + kwargs["extensions"] = kwargs.get("extensions", {}) + kwargs["extensions"]["http_version"] = http_version.encode("ascii") + super().__init__(status_code or 200, **kwargs) + + if content_type: + self.headers["Content-Type"] = content_type + + if cookies: + if isinstance(cookies, dict): + cookies = tuple(cookies.items()) + self.headers = httpx.Headers( + ( + *self.headers.multi_items(), + *( + cookie if isinstance(cookie, SetCookie) else SetCookie(*cookie) + for cookie in cookies + ), + ) + ) + + +class Route: + def __init__( + self, + *patterns: Pattern, + **lookups: Any, + ) -> None: + self._pattern = M(*patterns, **lookups) + self._return_value: Optional[httpx.Response] = None + self._side_effect: Optional[SideEffectTypes] = None + self._pass_through: bool = False + self._name: Optional[str] = None + self._snapshots: List[Tuple] = [] + self.calls = CallList(name=self) + self.snapshot() + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Route): + return False # pragma: nocover + return self.pattern == other.pattern + + def __repr__(self): # pragma: nocover + name = f"name={self._name!r} " if self._name else "" + return f"" + + def __call__(self, side_effect: CallableSideEffect) -> CallableSideEffect: + self.side_effect = side_effect + return side_effect + + def __mod__(self, response: Union[int, Dict[str, Any], httpx.Response]) -> "Route": + if isinstance(response, int): + self.return_value = httpx.Response(status_code=response) + + elif isinstance(response, dict): + response.setdefault("status_code", 200) + self.return_value = httpx.Response(**response) + + elif isinstance(response, httpx.Response): + self.return_value = response + + else: + raise TypeError( + f"Route can only % with int, dict or Response, got {response!r}" + ) + + return self + + @property + def name(self) -> Optional[str]: + return self._name + + @name.setter + def name(self, name: str) -> None: + raise NotImplementedError("Can't set name on route.") + + @property + def pattern(self) -> Pattern: + return self._pattern + + @pattern.setter + def pattern(self, pattern: Pattern) -> None: + raise NotImplementedError("Can't change route pattern.") + + @property + def return_value(self) -> Optional[httpx.Response]: + return self._return_value + + @return_value.setter + def return_value(self, return_value: Optional[httpx.Response]) -> None: + if return_value is not None and not isinstance(return_value, httpx.Response): + raise TypeError(f"{return_value!r} is not an instance of httpx.Response") + self.pass_through(False) + self._return_value = return_value + + @property + def side_effect( + self, + ) -> Optional[Union[SideEffectTypes, Sequence[SideEffectListTypes]]]: + return self._side_effect + + @side_effect.setter + def side_effect( + self, + side_effect: Optional[Union[SideEffectTypes, Sequence[SideEffectListTypes]]], + ) -> None: + self.pass_through(False) + if not side_effect: + self._side_effect = None + elif isinstance(side_effect, (Iterator, Sequence)): + self._side_effect = iter(side_effect) + else: + self._side_effect = side_effect + + def snapshot(self) -> None: + # Clone iterator-type side effect to not get pre-exhausted when rolled back + side_effect = self._side_effect + if isinstance(side_effect, Iterator): + side_effects = tuple(side_effect) + self._side_effect = iter(side_effects) + side_effect = iter(side_effects) + + self._snapshots.append( + ( + self._pattern, + self._name, + self._return_value, + side_effect, + self._pass_through, + CallList(self.calls, name=self), + ), + ) + + def rollback(self) -> None: + if not self._snapshots: + return + + snapshot = self._snapshots.pop() + pattern, name, return_value, side_effect, pass_through, calls = snapshot + + self._pattern = pattern + self._name = name + self._return_value = return_value + self._side_effect = side_effect + self.pass_through(pass_through) + self.calls[:] = calls + + def reset(self) -> None: + self.calls.clear() + + def mock( + self, + return_value: Optional[httpx.Response] = None, + *, + side_effect: Optional[ + Union[SideEffectTypes, Sequence[SideEffectListTypes]] + ] = None, + ) -> "Route": + self.return_value = return_value + self.side_effect = side_effect + return self + + def respond( + self, + status_code: int = 200, + *, + headers: Optional[HeaderTypes] = None, + cookies: Optional[Union[CookieTypes, Sequence[SetCookie]]] = None, + content: Optional[Content] = None, + text: Optional[str] = None, + html: Optional[str] = None, + json: Any = None, + stream: Optional[Union[httpx.SyncByteStream, httpx.AsyncByteStream]] = None, + content_type: Optional[str] = None, + http_version: Optional[str] = None, + **kwargs: Any, + ) -> "Route": + response = MockResponse( + status_code, + headers=headers, + cookies=cookies, + content=content, + text=text, + html=html, + json=json, + stream=stream, + content_type=content_type, + http_version=http_version, + **kwargs, + ) + return self.mock(return_value=response) + + def pass_through(self, value: bool = True) -> "Route": + self._pass_through = value + return self + + @property + def is_pass_through(self) -> bool: + return self._pass_through + + @property + def called(self) -> bool: + return self.calls.called + + @property + def call_count(self) -> int: + return self.calls.call_count + + def _next_side_effect( + self, + ) -> Union[CallableSideEffect, Exception, Type[Exception], httpx.Response]: + assert self._side_effect is not None + effect: Union[CallableSideEffect, Exception, Type[Exception], httpx.Response] + if isinstance(self._side_effect, Iterator): + effect = next(self._side_effect) + else: + effect = self._side_effect + + return effect + + def _call_side_effect( + self, effect: CallableSideEffect, request: httpx.Request, **kwargs: Any + ) -> RouteResultTypes: + # Add route kwarg if the side effect wants it + argspec = inspect.getfullargspec(effect) + if "route" in kwargs: + warn(f"Matched context contains reserved word `route`: {self.pattern!r}") + if "route" in argspec.args: + kwargs["route"] = self + + try: + # Call side effect + result: RouteResultTypes = effect(request, **kwargs) + except Exception as error: + raise SideEffectError(self, origin=error) from error + + # Validate result + if ( + result + and not inspect.isawaitable(result) + and not isinstance(result, (httpx.Response, httpx.Request)) + ): + raise TypeError( + f"Side effects must return; either a `httpx.Response`," + f"a `httpx.Request` for pass-through, " + f"or `None` for a non-match. Got {result!r}" + ) + + return result + + def _resolve_side_effect( + self, request: httpx.Request, **kwargs: Any + ) -> RouteResultTypes: + effect = self._next_side_effect() + + # Handle Exception `instance` side effect + if isinstance(effect, Exception): + raise SideEffectError(self, origin=effect) + + # Handle Exception `type` side effect + elif isinstance(effect, type): + assert issubclass(effect, Exception) + raise SideEffectError( + self, + origin=( + effect("Mock Error", request=request) + if issubclass(effect, httpx.RequestError) + else effect() + ), + ) + + # Handle `Callable` side effect + elif callable(effect): + result = self._call_side_effect(effect, request, **kwargs) + return result + + # Resolved effect is a mocked response + return effect + + def resolve(self, request: httpx.Request, **kwargs: Any) -> RouteResultTypes: + result: RouteResultTypes = None + + if self._side_effect: + result = self._resolve_side_effect(request, **kwargs) + if result is None: + return None # Side effect resolved as a non-matching route + + elif self._return_value: + result = self._return_value + + else: + # Auto mock a new response + result = httpx.Response(200, request=request) + + if isinstance(result, httpx.Response) and not result._request: + # Clone reused Response for immutability + result = clone_response(result, request) + + return result + + def match(self, request: httpx.Request) -> RouteResultTypes: + """ + Matches and resolves request with given patterns and optional side effect. + + Returns None for a non-matching route, mocked response for a match, + or input request for pass-through. + """ + context: Dict[str, Any] = {} + + if self._pattern: + match = self._pattern.match(request) + if not match: + return None + context = match.context + + if self._pass_through: + return request + + result = self.resolve(request, **context) + return result + + +class RouteList: + _routes: List[Route] + _names: Dict[str, Route] + + def __init__(self, routes: Optional["RouteList"] = None) -> None: + if routes is None: + self._routes = [] + self._names = {} + else: + self._routes = list(routes._routes) + self._names = dict(routes._names) + + def __repr__(self) -> str: + return repr(self._routes) # pragma: nocover + + def __iter__(self) -> Iterator[Route]: + return iter(self._routes) + + def __bool__(self) -> bool: + return bool(self._routes) + + def __len__(self) -> int: + return len(self._routes) + + def __contains__(self, name: str) -> bool: + return name in self._names + + def __getitem__(self, key: Union[int, str]) -> Route: + if isinstance(key, int): + return self._routes[key] + else: + return self._names[key] + + def __setitem__(self, i: slice, routes: "RouteList") -> None: + """ + Re-set all routes to given routes. + """ + if (i.start, i.stop, i.step) != (None, None, None): + raise TypeError("Can't slice assign routes") + self._routes = list(routes._routes) + self._names = dict(routes._names) + + def clear(self) -> None: + self._routes.clear() + self._names.clear() + + def add(self, route: Route, name: Optional[str] = None) -> Route: + # Find route with same name + existing_route = self._names.pop(name or "", None) + + if route in self._routes: + if existing_route and existing_route != route: + # Re-use existing route with same name, and drop any with same pattern + index = self._routes.index(route) + same_pattern_route = self._routes.pop(index) + if same_pattern_route.name: + del self._names[same_pattern_route.name] + same_pattern_route._name = None + elif not existing_route: + # Re-use existing route with same pattern + index = self._routes.index(route) + existing_route = self._routes[index] + if existing_route.name: + del self._names[existing_route.name] + existing_route._name = None + + if existing_route: + # Update existing route's pattern and mock + existing_route._pattern = route._pattern + existing_route.return_value = route.return_value + existing_route.side_effect = route.side_effect + existing_route.pass_through(route.is_pass_through) + route = existing_route + else: + # Add new route + self._routes.append(route) + + if name: + route._name = name + self._names[name] = route + + return route + + def pop(self, name, default=...): + """ + Removes a route by name and returns it. + + Raises KeyError when `default` not provided and name is not found. + """ + try: + route = self._names.pop(name) + self._routes.remove(route) + return route + except KeyError as ex: + if default is ...: + raise ex + return default + + +class AllMockedAssertionError(AssertionError): + pass + + +class SideEffectError(Exception): + def __init__(self, route: Route, origin: Exception) -> None: + self.route = route + self.origin = origin + + +class PassThrough(Exception): + def __init__(self, message: str, *, request: httpx.Request, origin: Route) -> None: + super().__init__(message) + self.request = request + self.origin = origin + + +class ResolvedRoute: + def __init__(self): + self.route: Optional[Route] = None + self.response: Optional[ResolvedResponseTypes] = None diff --git a/tests/respx2/patterns.py b/tests/respx2/patterns.py new file mode 100644 index 0000000000..7bcb43141a --- /dev/null +++ b/tests/respx2/patterns.py @@ -0,0 +1,782 @@ +import io +import json as jsonlib +import operator +import pathlib +import re +from abc import ABC +from enum import Enum +from functools import reduce +from http.cookies import SimpleCookie +from types import MappingProxyType +from typing import ( + Any, + Callable, + ClassVar, + Dict, + List, + Mapping, + Optional, + Pattern as RegexPattern, + Sequence, + Set, + Tuple, + Type, + Union, +) +from unittest.mock import ANY + +import httpx2 as httpx + +from tests.respx2.utils import MultiItems, decode_data + +from .types import ( + URL as RawURL, + CookieTypes, + FileTypes, + HeaderTypes, + QueryParamTypes, + RequestFiles, + URLPatternTypes, +) + + +class Lookup(Enum): + EQUAL = "eq" + REGEX = "regex" + STARTS_WITH = "startswith" + CONTAINS = "contains" + IN = "in" + + +class Match: + def __init__(self, matches: bool, **context: Any) -> None: + self.matches = matches + self.context = context + + def __bool__(self): + return bool(self.matches) + + def __invert__(self): + self.matches = not self.matches + return self + + def __repr__(self): # pragma: nocover + return f"" + + +class Pattern(ABC): + key: ClassVar[str] + lookups: ClassVar[Tuple[Lookup, ...]] = (Lookup.EQUAL,) + + lookup: Lookup + base: Optional["Pattern"] + value: Any + + # Automatically register all the subclasses in this dict + __registry: ClassVar[Dict[str, Type["Pattern"]]] = {} + registry = MappingProxyType(__registry) + + def __init_subclass__(cls) -> None: + if not getattr(cls, "key", None) or ABC in cls.__bases__: + return + + if cls.key in cls.__registry: + raise TypeError( + "Subclasses of Pattern must define a unique key. " + f"{cls.key!r} is already defined in {cls.__registry[cls.key]!r}" + ) + + cls.__registry[cls.key] = cls + + def __init__(self, value: Any, lookup: Optional[Lookup] = None) -> None: + if lookup and lookup not in self.lookups: + raise NotImplementedError( + f"{self.key!r} pattern does not support {lookup.value!r} lookup" + ) + self.lookup = lookup or self.lookups[0] + self.base = None + self.value = self.clean(value) + + def __iter__(self): + yield self + + def __bool__(self): + return True + + def __and__(self, other: "Pattern") -> "Pattern": + if not bool(other): + return self + elif not bool(self): + return other + return _And((self, other)) + + def __or__(self, other: "Pattern") -> "Pattern": + if not bool(other): + return self + elif not bool(self): + return other + return _Or((self, other)) + + def __invert__(self): + if not bool(self): + return self + return _Invert(self) + + def __repr__(self): # pragma: nocover + return f"<{self.__class__.__name__} {self.lookup.value} {repr(self.value)}>" + + def __hash__(self): + return hash((self.__class__, self.lookup, self.value)) + + def __eq__(self, other: object) -> bool: + return hash(self) == hash(other) + + def clean(self, value: Any) -> Any: + """ + Clean and return pattern value. + """ + return value + + def parse(self, request: httpx.Request) -> Any: # pragma: nocover + """ + Parse and return request value to match with pattern value. + """ + raise NotImplementedError() + + def strip_base(self, value: Any) -> Any: # pragma: nocover + return value + + def match(self, request: httpx.Request) -> Match: + try: + value = self.parse(request) + except Exception: + return Match(False) + + # Match and strip base + if self.base: + base_match = self.base._match(value) + if not base_match: + return base_match + value = self.strip_base(value) + + return self._match(value) + + def _match(self, value: Any) -> Match: + lookup_method = getattr(self, f"_{self.lookup.value}") + return lookup_method(value) + + def _eq(self, value: Any) -> Match: + return Match(value == self.value) + + def _regex(self, value: str) -> Match: + match = self.value.search(value) + if match is None: + return Match(False) + return Match(True, **match.groupdict()) + + def _startswith(self, value: str) -> Match: + return Match(value.startswith(self.value)) + + def _contains(self, value: Any) -> Match: # pragma: nocover + raise NotImplementedError() + + def _in(self, value: Any) -> Match: + return Match(value in self.value) + + +class Noop(Pattern): + def __init__(self) -> None: + super().__init__(None) + + def __repr__(self): + return f"<{self.__class__.__name__}>" + + def __bool__(self) -> bool: + # Treat this pattern as non-existent, e.g. when filtering or conditioning + return False + + def match(self, request: httpx.Request) -> Match: + # If this pattern is part of a combined pattern, always be truthy, i.e. noop + return Match(True) + + +class PathPattern(Pattern): + path: Optional[str] + + def __init__( + self, value: Any, lookup: Optional[Lookup] = None, *, path: Optional[str] = None + ) -> None: + self.path = path + super().__init__(value, lookup) + + +class _And(Pattern): + value: Tuple[Pattern, Pattern] + + def __repr__(self): # pragma: nocover + a, b = self.value + return f"{repr(a)} AND {repr(b)}" + + def __iter__(self): + a, b = self.value + yield from a + yield from b + + def match(self, request: httpx.Request) -> Match: + a, b = self.value + a_match = a.match(request) + if not a_match: + return a_match + b_match = b.match(request) + if not b_match: + return b_match + return Match(True, **{**a_match.context, **b_match.context}) + + +class _Or(Pattern): + value: Tuple[Pattern, Pattern] + + def __repr__(self): # pragma: nocover + a, b = self.value + return f"{repr(a)} OR {repr(b)}" + + def __iter__(self): + a, b = self.value + yield from a + yield from b + + def match(self, request: httpx.Request) -> Match: + a, b = self.value + match = a.match(request) + if not match: + match = b.match(request) + return match + + +class _Invert(Pattern): + value: Pattern + + def __repr__(self): # pragma: nocover + return f"NOT {repr(self.value)}" + + def __iter__(self): + yield from self.value + + def match(self, request: httpx.Request) -> Match: + return ~self.value.match(request) + + +class Method(Pattern): + key = "method" + lookups = (Lookup.EQUAL, Lookup.IN) + value: Union[str, Sequence[str]] + + def clean(self, value: Union[str, Sequence[str]]) -> Union[str, Sequence[str]]: + if isinstance(value, str): + value = value.upper() + else: + assert isinstance(value, Sequence) + value = tuple(v.upper() for v in value) + return value + + def parse(self, request: httpx.Request) -> str: + return request.method + + +class MultiItemsMixin: + lookup: Lookup + value: Any + + def _multi_items( + self, value: Any, *, parse_any: bool = False, encode_any: bool = False + ) -> Tuple[Tuple[str, Tuple[Any, ...]], ...]: + return tuple( + ( + key, + tuple( + self._item_value(v, parse_any=parse_any, encode_any=encode_any) + for v in value.get_list(key) + ), + ) + for key in sorted(value.keys()) + ) + + def _item_value( + self, value: Any, parse_any: bool = False, encode_any: bool = False + ) -> Any: + return ( + ANY + if parse_any and value == str(ANY) + else str(ANY) + if encode_any and value is ANY + else value + ) + + def __hash__(self): + return hash( + ( + self.__class__, + self.lookup, + self._multi_items(self.value, encode_any=True), + ) + ) + + def _eq(self, value: Any) -> Match: + value_items = self._multi_items(self.value, parse_any=True) + request_items = self._multi_items(value) + return Match(value_items == request_items) + + def _contains(self, value: Any) -> Match: + if len(self.value.multi_items()) > len(value.multi_items()): + return Match(False) + + value_items = self._multi_items(self.value, parse_any=True) + request_items = self._multi_items(value) + + for item in value_items: + if item not in request_items: + return Match(False) + + return Match(True) + + +class Headers(MultiItemsMixin, Pattern): + key = "headers" + lookups = (Lookup.CONTAINS, Lookup.EQUAL) + value: httpx.Headers + + def clean(self, value: HeaderTypes) -> httpx.Headers: + return httpx.Headers(value) + + def parse(self, request: httpx.Request) -> httpx.Headers: + return request.headers + + +class Cookies(Pattern): + key = "cookies" + lookups = (Lookup.CONTAINS, Lookup.EQUAL) + value: Set[Tuple[str, str]] + + def __hash__(self): + return hash((self.__class__, self.lookup, tuple(sorted(self.value)))) + + def clean(self, value: CookieTypes) -> Set[Tuple[str, str]]: + if isinstance(value, dict): + return set(value.items()) + + return set(value) + + def parse(self, request: httpx.Request) -> Set[Tuple[str, str]]: + headers = request.headers + + cookie_header = headers.get("cookie") + if not cookie_header: + return set() + + cookies: SimpleCookie = SimpleCookie() + cookies.load(rawdata=cookie_header) + + return {(cookie.key, cookie.value) for cookie in cookies.values()} + + def _contains(self, value: Set[Tuple[str, str]]) -> Match: + return Match(bool(self.value & value)) + + +class Scheme(Pattern): + key = "scheme" + lookups = (Lookup.EQUAL, Lookup.IN) + value: Union[str, Sequence[str]] + + def clean(self, value: Union[str, Sequence[str]]) -> Union[str, Sequence[str]]: + if isinstance(value, str): + value = value.lower() + else: + assert isinstance(value, Sequence) + value = tuple(v.lower() for v in value) + return value + + def parse(self, request: httpx.Request) -> str: + return request.url.scheme + + +class Host(Pattern): + key = "host" + lookups = (Lookup.EQUAL, Lookup.REGEX, Lookup.IN) + value: Union[str, RegexPattern[str], Sequence[str]] + + def clean( + self, value: Union[str, RegexPattern[str]] + ) -> Union[str, RegexPattern[str]]: + if self.lookup is Lookup.REGEX and isinstance(value, str): + value = re.compile(value) + return value + + def parse(self, request: httpx.Request) -> str: + return request.url.host + + +class Port(Pattern): + key = "port" + lookups = (Lookup.EQUAL, Lookup.IN) + value: Optional[int] + + def parse(self, request: httpx.Request) -> Optional[int]: + scheme = request.url.scheme + port = request.url.port + scheme_port = get_scheme_port(scheme) + return port or scheme_port + + +class Path(Pattern): + key = "path" + lookups = (Lookup.EQUAL, Lookup.REGEX, Lookup.STARTS_WITH, Lookup.IN) + value: Union[str, Sequence[str], RegexPattern[str]] + + def clean( + self, value: Union[str, RegexPattern[str]] + ) -> Union[str, RegexPattern[str]]: + if self.lookup in (Lookup.EQUAL, Lookup.STARTS_WITH) and isinstance(value, str): + # Percent encode path, i.e. revert parsed path by httpx.URL. + # Borrowed from HTTPX's "private" quote and percent_encode utilities. + path = "".join( + char + if char + in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~/" + else "".join(f"%{byte:02x}" for byte in char.encode("utf-8")).upper() + for char in value + ) + # Ensure a leading slash. Note we don't use urljoin because its + # behaviour with multiple slashes in the path is incorrect - see + # https://github.com/lundberg/respx/issues/273 + if not path.startswith("/"): + path = f"/{path}" + value = httpx.URL(path).path + elif self.lookup is Lookup.REGEX and isinstance(value, str): + value = re.compile(value) + return value + + def parse(self, request: httpx.Request) -> str: + return request.url.path + + def strip_base(self, value: str) -> str: + if self.base: + value = value[len(self.base.value) :] + value = "/" + value if not value.startswith("/") else value + return value + + +class Params(MultiItemsMixin, Pattern): + key = "params" + lookups = (Lookup.CONTAINS, Lookup.EQUAL) + value: httpx.QueryParams + + def clean(self, value: QueryParamTypes) -> httpx.QueryParams: + return httpx.QueryParams(value) + + def parse(self, request: httpx.Request) -> httpx.QueryParams: + query = request.url.query + return httpx.QueryParams(query) + + +class URL(Pattern): + key = "url" + lookups = ( + Lookup.EQUAL, + Lookup.REGEX, + Lookup.STARTS_WITH, + ) + value: Union[str, RegexPattern[str]] + + def clean(self, value: URLPatternTypes) -> Union[str, RegexPattern[str]]: + url: Union[str, RegexPattern[str]] + if self.lookup is Lookup.EQUAL and isinstance(value, (str, tuple, httpx.URL)): + _url = parse_url(value) + _url = self._ensure_path(_url) + url = str(_url) + elif self.lookup is Lookup.REGEX and isinstance(value, str): + url = re.compile(value) + elif isinstance(value, (str, RegexPattern)): + url = value + else: + raise ValueError(f"Invalid url: {value!r}") + return url + + def parse(self, request: httpx.Request) -> str: + url = request.url + url = self._ensure_path(url) + return str(url) + + def _ensure_path(self, url: httpx.URL) -> httpx.URL: + if not url._uri_reference.path: + url = url.copy_with(path="/") + return url + + +class ContentMixin: + def parse(self, request: httpx.Request) -> Any: + content = request.read() + return content + + +class Content(ContentMixin, Pattern): + lookups = (Lookup.EQUAL, Lookup.CONTAINS) + key = "content" + value: bytes + + def clean(self, value: Union[bytes, str]) -> bytes: + if isinstance(value, str): + return value.encode() + return value + + def _contains(self, value: Union[bytes, str]) -> Match: + return Match(self.value in value) + + +class JSON(ContentMixin, PathPattern): + lookups = (Lookup.EQUAL,) + key = "json" + value: str + + def clean(self, value: Union[str, List, Dict]) -> str: + return self.hash(value) + + def parse(self, request: httpx.Request) -> str: + content = super().parse(request) + json = jsonlib.loads(content.decode("utf-8")) + + if self.path: + value = json + for bit in self.path.split("__"): + key = int(bit) if bit.isdigit() else bit + try: + value = value[key] + except KeyError as e: + raise KeyError(f"{self.path!r} not in {json!r}") from e + except IndexError as e: + raise IndexError(f"{self.path!r} not in {json!r}") from e + else: + value = json + + return self.hash(value) + + def hash(self, value: Union[str, List, Dict]) -> str: + return jsonlib.dumps(value, sort_keys=True) + + +class Data(MultiItemsMixin, Pattern): + lookups = (Lookup.EQUAL, Lookup.CONTAINS) + key = "data" + value: MultiItems + + def _normalize_value(self, value: Any) -> Union[str, List[str]]: + if value is None: + return "" + elif isinstance(value, (tuple, list)): + return [str(v) for v in value] + else: + return str(value) + + def clean(self, value: Dict[str, Any]) -> MultiItems: + return MultiItems( + (key, self._normalize_value(value)) for key, value in value.items() + ) + + def parse(self, request: httpx.Request) -> Any: + data, _ = decode_data(request) + return data + + +class Files(MultiItemsMixin, Pattern): + lookups = (Lookup.CONTAINS, Lookup.EQUAL) + key = "files" + value: MultiItems + + def _normalize_file_value(self, value: FileTypes) -> Tuple[Tuple[Any, Any]]: + # Mimic httpx `FileField` to normalize `files` kwarg to shortest tuple style + if isinstance(value, tuple): + filename, fileobj = value[:2] + else: + try: + filename = pathlib.Path(str(getattr(value, "name"))).name # noqa: B009 + except AttributeError: + filename = ANY + fileobj = value + + # Normalize file-like objects and strings to bytes to allow equality check + if isinstance(fileobj, io.BytesIO): + fileobj = fileobj.read() + elif isinstance(fileobj, str): + fileobj = fileobj.encode() + + return ((filename, fileobj),) + + def _item_value( + self, value: Tuple[Any, Any], parse_any: bool = False, encode_any: bool = False + ) -> Tuple[Any, Any]: + filename, data = value + return ( + super()._item_value(filename, parse_any=parse_any, encode_any=encode_any), + super()._item_value(data, parse_any=parse_any, encode_any=encode_any), + ) + + def clean(self, value: RequestFiles) -> MultiItems: + if isinstance(value, Mapping): + value = list(value.items()) + + files = MultiItems( + (name, self._normalize_file_value(file_value)) for name, file_value in value + ) + return files + + def parse(self, request: httpx.Request) -> Any: + _, files = decode_data(request) + return files + + +def M(*patterns: Pattern, **lookups: Any) -> Pattern: + extras = None + + for pattern__lookup, value in lookups.items(): + # Handle url pattern + if pattern__lookup == "url": + extras = parse_url_patterns(value) + continue + + # Parse pattern key and lookup + pattern_key, __, rest = pattern__lookup.partition("__") + path, __, lookup_name = rest.rpartition("__") + if pattern_key not in Pattern.registry: + raise KeyError(f"{pattern_key!r} is not a valid Pattern") + + # Get pattern class + P = Pattern.registry[pattern_key] + pattern: Union[Pattern, PathPattern] + + if issubclass(P, PathPattern): + # Make path supported pattern, i.e. JSON + try: + lookup = Lookup(lookup_name) if lookup_name else None + except ValueError: + lookup = None + path = rest + pattern = P(value, lookup=lookup, path=path) + else: + # Make regular pattern + lookup = Lookup(lookup_name) if lookup_name else None + pattern = P(value, lookup=lookup) + + # Skip patterns with no value, exept when using equal lookup + if not pattern.value and pattern.lookup is not Lookup.EQUAL: + continue + + patterns += (pattern,) + + # Combine and merge patterns + combined_pattern = combine(patterns) + if extras: + combined_pattern = merge_patterns(combined_pattern, **extras) + + return combined_pattern + + +def get_scheme_port(scheme: Optional[str]) -> Optional[int]: + return {"http": 80, "https": 443}.get(scheme or "") + + +def combine(patterns: Sequence[Pattern], op: Callable = operator.and_) -> Pattern: + patterns = tuple(filter(None, patterns)) + if not patterns: + return Noop() + return reduce(op, patterns) + + +def parse_url(value: Union[httpx.URL, str, RawURL]) -> httpx.URL: + url: Union[httpx.URL, str] + + if isinstance(value, tuple): + # Handle "raw" httpcore urls. Borrowed from HTTPX prior to #2241 + raw_scheme, raw_host, port, raw_path = value + scheme = raw_scheme.decode("ascii") + host = raw_host.decode("ascii") + if host and ":" in host and host[0] != "[": + # it's an IPv6 address, so it should be enclosed in "[" and "]" + # ref: https://tools.ietf.org/html/rfc2732#section-2 + # ref: https://tools.ietf.org/html/rfc3986#section-3.2.2 + host = f"[{host}]" + port_str = "" if port is None else f":{port}" + path = raw_path.decode("ascii") + url = f"{scheme}://{host}{port_str}{path}" + else: + url = value + + return httpx.URL(url) + + +def parse_url_patterns( + url: Optional[URLPatternTypes], exact: bool = True +) -> Dict[str, Pattern]: + bases: Dict[str, Pattern] = {} + if not url or url == "all": + return bases + + if isinstance(url, RegexPattern): + return {"url": URL(url, lookup=Lookup.REGEX)} + + url = parse_url(url) + scheme_port = get_scheme_port(url.scheme) + + if url.scheme and url.scheme != "all": + bases[Scheme.key] = Scheme(url.scheme) + if url.host: + # NOTE: Host regex patterns borrowed from HTTPX source to support proxy format + if url.host.startswith("*."): + domain = re.escape(url.host[2:]) + regex = re.compile(f"^.+\\.{domain}$") + bases[Host.key] = Host(regex, lookup=Lookup.REGEX) + elif url.host.startswith("*"): + domain = re.escape(url.host[1:]) + regex = re.compile(f"^(.+\\.)?{domain}$") + bases[Host.key] = Host(regex, lookup=Lookup.REGEX) + else: + bases[Host.key] = Host(url.host) + if url.port and url.port != scheme_port: + bases[Port.key] = Port(url.port) + if url._uri_reference.path: # URL.path always returns "/" + lookup = Lookup.EQUAL if exact else Lookup.STARTS_WITH + bases[Path.key] = Path(url.path, lookup=lookup) + if url.query: + lookup = Lookup.EQUAL if exact else Lookup.CONTAINS + bases[Params.key] = Params(url.query, lookup=lookup) + + return bases + + +def merge_patterns(pattern: Pattern, **bases: Pattern) -> Pattern: + if not bases: + return pattern + + # Flatten pattern + patterns: List[Pattern] = list(filter(None, iter(pattern))) + + if patterns: + if "host" in (_pattern.key for _pattern in patterns): + # Pattern is "absolute", skip merging + bases = {} + else: + # Traverse pattern and set related base + for _pattern in patterns: + base = bases.pop(_pattern.key, None) + # Skip "exact" base + don't overwrite existing base + if _pattern.base or base and base.lookup is Lookup.EQUAL: + continue + _pattern.base = base + + if bases: + # Combine left over base patterns with pattern + base_pattern = combine(list(bases.values())) + if pattern and base_pattern: + pattern = base_pattern & pattern + else: + pattern = base_pattern + + return pattern diff --git a/tests/respx2/plugin.py b/tests/respx2/plugin.py new file mode 100644 index 0000000000..81e2759b32 --- /dev/null +++ b/tests/respx2/plugin.py @@ -0,0 +1,30 @@ +from typing import cast + +import pytest + +from tests import respx2 as respx + +from .router import MockRouter + + +def pytest_configure(config): + config.addinivalue_line( + "markers", + "respx2(assert_all_called=False, assert_all_mocked=False, base_url=...): " + "configure the respx2_mock fixture. " + "See https://lundberg.github.io/respx/api.html#configuration", + ) + + +@pytest.fixture +def respx2_mock(request): + respx_marker = request.node.get_closest_marker("respx2") + + mock_router: MockRouter = ( + respx.mock + if respx_marker is None + else cast(MockRouter, respx.mock(**respx_marker.kwargs)) + ) + + with mock_router: + yield mock_router diff --git a/tests/respx2/py.typed b/tests/respx2/py.typed new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/respx2/router.py b/tests/respx2/router.py new file mode 100644 index 0000000000..fc1d6e22e0 --- /dev/null +++ b/tests/respx2/router.py @@ -0,0 +1,486 @@ +import inspect +from contextlib import contextmanager +from functools import partial, update_wrapper, wraps +from types import TracebackType +from typing import ( + Any, + Callable, + Dict, + Generator, + List, + NewType, + Optional, + Tuple, + Type, + Union, + cast, + overload, +) + +import httpx2 as httpx + +from .mocks import Mocker +from .models import ( + AllMockedAssertionError, + CallList, + PassThrough, + ResolvedRoute, + Route, + RouteList, + SideEffectError, +) +from .patterns import Pattern, merge_patterns, parse_url_patterns +from .types import DefaultType, ResolvedResponseTypes, RouteResultTypes, URLPatternTypes + +Default = NewType("Default", object) +DEFAULT = Default(...) + + +class Router: + def __init__( + self, + *, + assert_all_called: bool = True, + assert_all_mocked: bool = True, + base_url: Optional[str] = None, + ) -> None: + self._assert_all_called = assert_all_called + self._assert_all_mocked = assert_all_mocked + self._bases = parse_url_patterns(base_url, exact=False) + + self.routes = RouteList() + self.calls = CallList() + + self._snapshots: List[Tuple] = [] + self.snapshot() + + def clear(self) -> None: + """ + Clears all routes. May be rolled back to snapshot state. + """ + self.routes.clear() + + def snapshot(self) -> None: + """ + Snapshots current routes and calls state. + """ + # Snapshot current routes and calls + routes = RouteList(self.routes) + calls = CallList(self.calls) + self._snapshots.append((routes, calls)) + + # Snapshot each route state + for route in routes: + route.snapshot() + + def rollback(self) -> None: + """ + Rollbacks routes, and optionally calls, to snapshot state. + """ + if not self._snapshots: + return + + # Revert added routes and calls to last snapshot + routes, calls = self._snapshots.pop() + self.routes[:] = routes + self.calls[:] = calls + + # Revert each route state to last snapshot + for route in self.routes: + route.rollback() + + def reset(self) -> None: + """ + Resets call stats. + """ + self.calls.clear() + for route in self.routes: + route.reset() + + def assert_all_called(self) -> None: + not_called_routes = [route for route in self.routes if not route.called] + assert not_called_routes == [], "RESPX: some routes were not called!" + + def __getitem__(self, name: str) -> Route: + return self.routes[name] + + @overload + def pop(self, name: str) -> Route: + ... # pragma: nocover + + @overload + def pop(self, name: str, default: DefaultType) -> Union[Route, DefaultType]: + ... # pragma: nocover + + def pop(self, name, default=...): + """ + Removes a route by name and returns it. + + Raises KeyError when `default` not provided and name is not found. + """ + try: + return self.routes.pop(name) + except KeyError as ex: + if default is ...: + raise ex + return default + + def route( + self, *patterns: Pattern, name: Optional[str] = None, **lookups: Any + ) -> Route: + route = Route(*patterns, **lookups) + return self.add(route, name=name) + + def add(self, route: Route, *, name: Optional[str] = None) -> Route: + """ + Adds a route with optionally given name, + replacing any existing route with same name or pattern. + """ + if not isinstance(route, Route): + raise ValueError( + f"Invalid route {route!r}, please use respx.route(...).mock(...)" + ) + + route._pattern = merge_patterns(route.pattern, **self._bases) + route = self.routes.add(route, name=name) + return route + + def request( + self, + method: str, + url: Optional[URLPatternTypes] = None, + *, + name: Optional[str] = None, + **lookups: Any, + ) -> Route: + if lookups: + # Validate that lookups doesn't contain method or url + pattern_keys = {p.split("__", 1)[0] for p in lookups.keys()} + if "method" in pattern_keys: + raise TypeError("Got multiple values for pattern 'method'") + elif url and "url" in pattern_keys: + raise TypeError("Got multiple values for pattern 'url'") + + return self.route(method=method, url=url, name=name, **lookups) + + def get( + self, + url: Optional[URLPatternTypes] = None, + *, + name: Optional[str] = None, + **lookups: Any, + ) -> Route: + return self.request(method="GET", url=url, name=name, **lookups) + + def post( + self, + url: Optional[URLPatternTypes] = None, + *, + name: Optional[str] = None, + **lookups: Any, + ) -> Route: + return self.request(method="POST", url=url, name=name, **lookups) + + def put( + self, + url: Optional[URLPatternTypes] = None, + *, + name: Optional[str] = None, + **lookups: Any, + ) -> Route: + return self.request(method="PUT", url=url, name=name, **lookups) + + def patch( + self, + url: Optional[URLPatternTypes] = None, + *, + name: Optional[str] = None, + **lookups: Any, + ) -> Route: + return self.request(method="PATCH", url=url, name=name, **lookups) + + def delete( + self, + url: Optional[URLPatternTypes] = None, + *, + name: Optional[str] = None, + **lookups: Any, + ) -> Route: + return self.request(method="DELETE", url=url, name=name, **lookups) + + def head( + self, + url: Optional[URLPatternTypes] = None, + *, + name: Optional[str] = None, + **lookups: Any, + ) -> Route: + return self.request(method="HEAD", url=url, name=name, **lookups) + + def options( + self, + url: Optional[URLPatternTypes] = None, + *, + name: Optional[str] = None, + **lookups: Any, + ) -> Route: + return self.request(method="OPTIONS", url=url, name=name, **lookups) + + def record( + self, + request: httpx.Request, + *, + response: Optional[httpx.Response] = None, + route: Optional[Route] = None, + ) -> None: + call = self.calls.record(request, response) + if route: + route.calls.append(call) + + @contextmanager + def resolver(self, request: httpx.Request) -> Generator[ResolvedRoute, None, None]: + resolved = ResolvedRoute() + + try: + yield resolved + + if resolved.route is None: + # Assert we always get a route match, if check is enabled + if self._assert_all_mocked: + raise AllMockedAssertionError(f"RESPX: {request!r} not mocked!") + + # Auto mock a successful empty response + resolved.response = httpx.Response(200) + + elif resolved.response == request: + # Pass-through request + raise PassThrough( + f"Request marked to pass through: {request!r}", + request=request, + origin=resolved.route, + ) + + else: + # Mocked response + assert isinstance(resolved.response, httpx.Response) + + except SideEffectError as error: + self.record(request, response=None, route=error.route) + raise error.origin from error + except PassThrough: + self.record(request, response=None, route=resolved.route) + raise + else: + self.record(request, response=resolved.response, route=resolved.route) + + def resolve(self, request: httpx.Request) -> ResolvedRoute: + with self.resolver(request) as resolved: + for route in self.routes: + prospect = route.match(request) + if prospect is not None: + resolved.route = route + resolved.response = cast(ResolvedResponseTypes, prospect) + break + + if resolved.response and isinstance(resolved.response.stream, httpx.ByteStream): + resolved.response.read() # Pre-read stream + + return resolved + + async def aresolve(self, request: httpx.Request) -> ResolvedRoute: + with self.resolver(request) as resolved: + for route in self.routes: + prospect: RouteResultTypes = route.match(request) + + # Await async side effect and wrap any exception + if inspect.isawaitable(prospect): + try: + prospect = await prospect + except Exception as error: + raise SideEffectError(route, origin=error) from error + + if prospect is not None: + resolved.route = route + resolved.response = cast(ResolvedResponseTypes, prospect) + break + + if resolved.response and isinstance(resolved.response.stream, httpx.ByteStream): + await resolved.response.aread() # Pre-read stream + + return resolved + + def handler(self, request: httpx.Request) -> httpx.Response: + resolved = self.resolve(request) + assert isinstance(resolved.response, httpx.Response) + return resolved.response + + async def async_handler(self, request: httpx.Request) -> httpx.Response: + resolved = await self.aresolve(request) + assert isinstance(resolved.response, httpx.Response) + return resolved.response + + +class MockRouter(Router): + def __init__( + self, + *, + assert_all_called: bool = True, + assert_all_mocked: bool = True, + base_url: Optional[str] = None, + using: Optional[Union[str, Default]] = DEFAULT, + ) -> None: + super().__init__( + assert_all_called=assert_all_called, + assert_all_mocked=assert_all_mocked, + base_url=base_url, + ) + self.Mocker: Optional[Type[Mocker]] = None + self._using = using + + @overload + def __call__( + self, + func: None = None, + *, + assert_all_called: Optional[bool] = None, + assert_all_mocked: Optional[bool] = None, + base_url: Optional[str] = None, + using: Optional[Union[str, Default]] = DEFAULT, + ) -> "MockRouter": + ... # pragma: nocover + + @overload + def __call__( + self, + func: Callable = ..., + *, + assert_all_called: Optional[bool] = None, + assert_all_mocked: Optional[bool] = None, + base_url: Optional[str] = None, + using: Optional[Union[str, Default]] = DEFAULT, + ) -> Callable: + ... # pragma: nocover + + def __call__( + self, + func: Optional[Callable] = None, + *, + assert_all_called: Optional[bool] = None, + assert_all_mocked: Optional[bool] = None, + base_url: Optional[str] = None, + using: Optional[Union[str, Default]] = DEFAULT, + ) -> Union["MockRouter", Callable]: + """ + Decorator or Context Manager. + + Use decorator/manager with parentheses for local state, or without parentheses + for global state, i.e. shared patterns added outside of scope. + """ + if func is None: + # Parentheses used, branch out to new nested instance. + # - Only stage when using local ctx `with respx.mock(...) as respx_mock:` + # - First stage when using local decorator `@respx.mock(...)` + # FYI, global ctx `with respx.mock:` hits __enter__ directly + settings: Dict[str, Any] = { + "base_url": base_url, + "using": using, + } + if assert_all_called is not None: + settings["assert_all_called"] = assert_all_called + if assert_all_mocked is not None: + settings["assert_all_mocked"] = assert_all_mocked + respx_mock = self.__class__(**settings) + return respx_mock + + # Determine if decorated function needs a `respx_mock` instance + is_async = inspect.iscoroutinefunction(func) + argspec = inspect.getfullargspec(func) + needs_mock_reference = "respx_mock" in argspec.args + + if needs_mock_reference: + func = partial(func, respx_mock=self) + + # Async Decorator + async def _async_decorator(*args, **kwargs): + assert func is not None + async with self: + return await func(*args, **kwargs) + + # Sync Decorator + def _sync_decorator(*args, **kwargs): + assert func is not None + with self: + return func(*args, **kwargs) + + if needs_mock_reference: + async_decorator = wraps(func)(_async_decorator) + sync_decorator = wraps(func)(_sync_decorator) + else: + async_decorator = update_wrapper(_async_decorator, func) + sync_decorator = update_wrapper(_sync_decorator, func) + + # Dispatch async/sync decorator, depending on decorated function. + # - Only stage when using global decorator `@respx.mock` + # - Second stage when using local decorator `@respx.mock(...)` + return async_decorator if is_async else sync_decorator + + def __enter__(self) -> "MockRouter": + self.start() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]] = None, + exc_value: Optional[BaseException] = None, + traceback: Optional[TracebackType] = None, + ) -> None: + self.stop(quiet=bool(exc_type is not None)) + + async def __aenter__(self) -> "MockRouter": + return self.__enter__() + + async def __aexit__(self, *args: Any) -> None: + self.__exit__(*args) + + @property + def using(self) -> Optional[str]: + from tests.respx2.mocks import DEFAULT_MOCKER + + if self._using is None: + using = None + elif self._using is DEFAULT: + using = DEFAULT_MOCKER + elif isinstance(self._using, str): + using = self._using + else: + raise ValueError(f"Invalid Router `using` kwarg: {self._using!r}") + + return using + + def start(self) -> None: + """ + Register transport, snapshot router and start patching. + """ + self.snapshot() + self.Mocker = Mocker.registry.get(self.using or "") + if self.Mocker: + self.Mocker.register(self) + self.Mocker.start() + + def stop(self, clear: bool = True, reset: bool = True, quiet: bool = False) -> None: + """ + Unregister transport and rollback router. + Stop patching when no registered transports left. + """ + unregistered = self.Mocker.unregister(self) if self.Mocker else True + + try: + if unregistered and not quiet and self._assert_all_called: + self.assert_all_called() + finally: + if clear: + self.rollback() + if reset: + self.reset() + if self.Mocker: + self.Mocker.stop() diff --git a/tests/respx2/transports.py b/tests/respx2/transports.py new file mode 100644 index 0000000000..7fb2c5d2db --- /dev/null +++ b/tests/respx2/transports.py @@ -0,0 +1,93 @@ +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Coroutine, + List, + Optional, + Type, + Union, + cast, +) +from warnings import warn + +import httpx2 as httpx +from httpx2 import AsyncBaseTransport, BaseTransport + +from .models import PassThrough + +if TYPE_CHECKING: + from .router import Router # pragma: nocover + +RequestHandler = Callable[[httpx.Request], httpx.Response] +AsyncRequestHandler = Callable[[httpx.Request], Coroutine[None, None, httpx.Response]] + + +class MockTransport(httpx.MockTransport): + _router: Optional["Router"] + + def __init__( + self, + *, + handler: Optional[RequestHandler] = None, + async_handler: Optional[AsyncRequestHandler] = None, + router: Optional["Router"] = None, + ): + if router: + super().__init__(router.handler) + self._router = router + elif handler: + super().__init__(handler) + self._router = None + elif async_handler: + super().__init__(async_handler) + self._router = None + else: + raise RuntimeError( + "Missing a MockTransport required handler or router argument" + ) + warn( + "MockTransport is deprecated. " + "Please use `httpx.MockTransport(respx_router.handler)`.", + category=DeprecationWarning, + ) + + def __exit__( + self, + exc_type: Optional[Type[BaseException]] = None, + exc_value: Optional[BaseException] = None, + traceback: Optional[TracebackType] = None, + ) -> None: + if not exc_type and self._router and self._router._assert_all_called: + self._router.assert_all_called() + + async def __aexit__(self, *args: Any) -> None: + self.__exit__(*args) + + +class TryTransport(BaseTransport, AsyncBaseTransport): + def __init__( + self, transports: List[Union[BaseTransport, AsyncBaseTransport]] + ) -> None: + self.transports = transports + + def handle_request(self, request: httpx.Request) -> httpx.Response: + for transport in self.transports: + try: + transport = cast(BaseTransport, transport) + return transport.handle_request(request) + except PassThrough: + continue + + raise RuntimeError() # pragma: nocover + + async def handle_async_request(self, request: httpx.Request) -> httpx.Response: + for transport in self.transports: + try: + transport = cast(AsyncBaseTransport, transport) + return await transport.handle_async_request(request) + except PassThrough: + continue + + raise RuntimeError() # pragma: nocover diff --git a/tests/respx2/types.py b/tests/respx2/types.py new file mode 100644 index 0000000000..41956308f0 --- /dev/null +++ b/tests/respx2/types.py @@ -0,0 +1,71 @@ +from typing import ( + IO, + Any, + AsyncIterable, + Awaitable, + Callable, + Dict, + Iterable, + Iterator, + List, + Mapping, + Optional, + Pattern, + Sequence, + Tuple, + Type, + TypeVar, + Union, +) + +import httpx2 as httpx + +URL = Tuple[ + bytes, # scheme + bytes, # host + Optional[int], # port + bytes, # path +] +Headers = List[Tuple[bytes, bytes]] +Content = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] + +HeaderTypes = Union[ + httpx.Headers, + Dict[str, str], + Dict[bytes, bytes], + Sequence[Tuple[str, str]], + Sequence[Tuple[bytes, bytes]], +] +CookieTypes = Union[Dict[str, str], Sequence[Tuple[str, str]]] + +DefaultType = TypeVar("DefaultType", bound=Any) + +URLPatternTypes = Union[str, Pattern[str], URL, httpx.URL] +QueryParamTypes = Union[ + bytes, str, List[Tuple[str, Any]], Dict[str, Any], Tuple[Tuple[str, Any], ...] +] + +ResolvedResponseTypes = Optional[Union[httpx.Request, httpx.Response]] +RouteResultTypes = Union[ResolvedResponseTypes, Awaitable[ResolvedResponseTypes]] +CallableSideEffect = Callable[..., RouteResultTypes] +SideEffectListTypes = Union[httpx.Response, Exception, Type[Exception]] +SideEffectTypes = Union[ + CallableSideEffect, + Exception, + Type[Exception], + Iterator[SideEffectListTypes], +] + +# Borrowed from HTTPX's "private" types. +FileContent = Union[IO[bytes], bytes, str] +FileTypes = Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], FileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], FileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], +] +RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]] diff --git a/tests/respx2/utils.py b/tests/respx2/utils.py new file mode 100644 index 0000000000..085d0c3b21 --- /dev/null +++ b/tests/respx2/utils.py @@ -0,0 +1,157 @@ +import email +from collections import defaultdict +from datetime import datetime +from email.message import Message +from typing import ( + Any, + Dict, + Iterable, + List, + Literal, + NamedTuple, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) +from urllib.parse import parse_qsl + +import httpx2 as httpx + + +class MultiItems(defaultdict): + def __init__(self, values: Optional[Iterable[Tuple[str, Any]]] = None) -> None: + super().__init__(tuple) + if values is not None: + for key, value in values: + if isinstance(value, (tuple, list)): + self[key] += tuple(value) # Convert list to tuple and extend + else: + self[key] += (value,) # Extend with value + + def get_list(self, key: str) -> List[Any]: + return list(self[key]) + + def multi_items(self) -> List[Tuple[str, str]]: + return [(key, value) for key, values in self.items() for value in values] + + def append(self, key: str, value: Any) -> None: + self[key] += (value,) + + +def _parse_multipart_form_data( + content: bytes, *, content_type: str, encoding: str +) -> Tuple[MultiItems, MultiItems]: + form_data = b"\r\n".join( + ( + b"MIME-Version: 1.0", + b"Content-Type: " + content_type.encode(encoding), + b"\r\n" + content, + ) + ) + data = MultiItems() + files = MultiItems() + for payload in email.message_from_bytes(form_data).get_payload(): + payload = cast(Message, payload) + name = payload.get_param("name", header="Content-Disposition") + assert isinstance(name, str) + filename = payload.get_filename() + content_type = payload.get_content_type() + value = payload.get_payload(decode=True) + assert isinstance(value, bytes) + if content_type.startswith("text/") and filename is None: + # Text field + data.append(name, value.decode(payload.get_content_charset() or "utf-8")) + else: + # File field + files.append(name, (filename, value)) + + return data, files + + +def _parse_urlencoded_data(content: bytes, *, encoding: str) -> MultiItems: + return MultiItems( + (key, value) + for key, value in parse_qsl(content.decode(encoding), keep_blank_values=True) + ) + + +def decode_data(request: httpx.Request) -> Tuple[MultiItems, MultiItems]: + content = request.read() + content_type = request.headers.get("Content-Type", "") + + if content_type.startswith("multipart/form-data"): + data, files = _parse_multipart_form_data( + content, + content_type=content_type, + encoding=request.headers.encoding, + ) + else: + data = _parse_urlencoded_data( + content, + encoding=request.headers.encoding, + ) + files = MultiItems() + + return data, files + + +Self = TypeVar("Self", bound="SetCookie") + + +class SetCookie( + NamedTuple( + "SetCookie", + [ + ("header_name", Literal["Set-Cookie"]), + ("header_value", str), + ], + ) +): + def __new__( + cls: Type[Self], + name: str, + value: str, + *, + path: Optional[str] = None, + domain: Optional[str] = None, + expires: Optional[Union[str, datetime]] = None, + max_age: Optional[int] = None, + http_only: bool = False, + same_site: Optional[Literal["Strict", "Lax", "None"]] = None, + secure: bool = False, + partitioned: bool = False, + ) -> Self: + """ + https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#syntax + """ + attrs: Dict[str, Union[str, bool]] = {name: value} + if path is not None: + attrs["Path"] = path + if domain is not None: + attrs["Domain"] = domain + if expires is not None: + if isinstance(expires, datetime): # pragma: no branch + expires = expires.strftime("%a, %d %b %Y %H:%M:%S GMT") + attrs["Expires"] = expires + if max_age is not None: + attrs["Max-Age"] = str(max_age) + if http_only: + attrs["HttpOnly"] = True + if same_site is not None: + attrs["SameSite"] = same_site + if same_site == "None": # pragma: no branch + secure = True + if secure: + attrs["Secure"] = True + if partitioned: + attrs["Partitioned"] = True + + string = "; ".join( + _name if _value is True else f"{_name}={_value}" + for _name, _value in attrs.items() + ) + self = super().__new__(cls, "Set-Cookie", string) + return self diff --git a/tests/test_auth.py b/tests/test_auth.py index 17e9cd814c..f9b1719cb5 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -2,13 +2,13 @@ from typing import cast from pathlib import Path -import httpx -import respx +import httpx2 import pytest -from respx.models import Call from inline_snapshot import snapshot +from tests import respx2 from openai import OpenAI, OAuthError +from tests.respx2.models import Call from openai.auth._workload import ( gcp_id_token_provider, k8s_service_account_token_provider, @@ -16,10 +16,10 @@ ) -@respx.mock +@respx2.mock def test_basic_auth(): - respx.post("https://auth.openai.com/oauth/token").mock( - return_value=httpx.Response( + respx2.post("https://auth.openai.com/oauth/token").mock( + return_value=httpx2.Response( 200, json={ "access_token": "fake_access_token", @@ -30,8 +30,8 @@ def test_basic_auth(): ) ) - respx.get("https://api.openai.com/v1/models").mock( - return_value=httpx.Response(200, json={"data": [], "object": "list"}) + respx2.get("https://api.openai.com/v1/models").mock( + return_value=httpx2.Response(200, json={"data": [], "object": "list"}) ) client = OpenAI( @@ -47,15 +47,15 @@ def test_basic_auth(): client.models.list() - assert len(respx.calls) == 2 - token_call = cast(Call, respx.calls[0]) - api_call = cast(Call, respx.calls[1]) + assert len(respx2.calls) == 2 + token_call = cast(Call, respx2.calls[0]) + api_call = cast(Call, respx2.calls[1]) assert token_call.request.url == "https://auth.openai.com/oauth/token" assert api_call.request.headers.get("Authorization") == "Bearer fake_access_token" -@respx.mock +@respx2.mock def test_workload_identity_exchange_payload_and_cache() -> None: provider_call_count = 0 @@ -64,8 +64,8 @@ def provider() -> str: provider_call_count += 1 return "fake_subject_token" - exchange_route = respx.post("https://auth.openai.com/oauth/token").mock( - return_value=httpx.Response( + exchange_route = respx2.post("https://auth.openai.com/oauth/token").mock( + return_value=httpx2.Response( 200, json={ "access_token": "fake_access_token", @@ -75,8 +75,8 @@ def provider() -> str: }, ) ) - api_route = respx.get("https://api.openai.com/v1/models").mock( - return_value=httpx.Response(200, json={"data": [], "object": "list"}) + api_route = respx2.get("https://api.openai.com/v1/models").mock( + return_value=httpx2.Response(200, json={"data": [], "object": "list"}) ) client = OpenAI( @@ -97,7 +97,7 @@ def provider() -> str: assert exchange_route.call_count == 1 assert api_route.call_count == 2 - exchange_request = cast(respx.models.Call, exchange_route.calls[0]).request + exchange_request = cast(respx2.models.Call, exchange_route.calls[0]).request assert json.loads(exchange_request.content) == snapshot( { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", @@ -109,17 +109,17 @@ def provider() -> str: ) assert ( - cast(respx.models.Call, api_route.calls[0]).request.headers.get("Authorization") == "Bearer fake_access_token" + cast(respx2.models.Call, api_route.calls[0]).request.headers.get("Authorization") == "Bearer fake_access_token" ) assert ( - cast(respx.models.Call, api_route.calls[1]).request.headers.get("Authorization") == "Bearer fake_access_token" + cast(respx2.models.Call, api_route.calls[1]).request.headers.get("Authorization") == "Bearer fake_access_token" ) -@respx.mock +@respx2.mock def test_workload_identity_exchange_error() -> None: - exchange_route = respx.post("https://auth.openai.com/oauth/token").mock( - return_value=httpx.Response( + exchange_route = respx2.post("https://auth.openai.com/oauth/token").mock( + return_value=httpx2.Response( 401, json={ "error": "invalid_grant", @@ -127,8 +127,8 @@ def test_workload_identity_exchange_error() -> None: }, ) ) - api_route = respx.get("https://api.openai.com/v1/models").mock( - return_value=httpx.Response(200, json={"data": [], "object": "list"}) + api_route = respx2.get("https://api.openai.com/v1/models").mock( + return_value=httpx2.Response(200, json={"data": [], "object": "list"}) ) client = OpenAI( @@ -162,10 +162,10 @@ def test_k8s_service_account_token_provider(tmp_path: Path) -> None: assert provider["get_token"]() == "my-k8s-token" -@respx.mock +@respx2.mock def test_azure_managed_identity_token_provider() -> None: - respx.get("http://169.254.169.254/metadata/identity/oauth2/token").mock( - return_value=httpx.Response(200, json={"access_token": "azure-token"}) + respx2.get("http://169.254.169.254/metadata/identity/oauth2/token").mock( + return_value=httpx2.Response(200, json={"access_token": "azure-token"}) ) provider = azure_managed_identity_token_provider() @@ -174,10 +174,10 @@ def test_azure_managed_identity_token_provider() -> None: assert provider["get_token"]() == "azure-token" -@respx.mock +@respx2.mock def test_gcp_id_token_provider() -> None: - respx.get("http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity").mock( - return_value=httpx.Response(200, text="gcp-token") + respx2.get("http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity").mock( + return_value=httpx2.Response(200, text="gcp-token") ) provider = gcp_id_token_provider() diff --git a/tests/test_client.py b/tests/test_client.py index 33a5b1c224..092050987d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -14,14 +14,13 @@ from unittest import mock from typing_extensions import Literal, AsyncIterator, override -import httpx +import httpx2 import pytest -from respx import MockRouter from pydantic import ValidationError -from respx.models import Call as MockRequestCall from openai import OpenAI, AsyncOpenAI, OpenAIError, APIResponseValidationError from openai.auth import WorkloadIdentity +from tests.respx2 import MockRouter from openai._types import Omit from openai._utils import asyncify from openai._models import BaseModel, FinalRequestOptions @@ -37,6 +36,7 @@ get_platform, make_request_options, ) +from tests.respx2.models import Call as MockRequestCall from .utils import update_env @@ -56,7 +56,7 @@ def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - url = httpx.URL(request.url) + url = httpx2.URL(request.url) return dict(url.params) @@ -64,25 +64,25 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: return 0.1 -def mirror_request_content(request: httpx.Request) -> httpx.Response: - return httpx.Response(200, content=request.content) +def mirror_request_content(request: httpx2.Request) -> httpx2.Response: + return httpx2.Response(200, content=request.content) # note: we can't use the httpx.MockTransport class as it consumes the request # body itself, which means we can't test that the body is read lazily -class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport): +class MockTransport(httpx2.BaseTransport, httpx2.AsyncBaseTransport): def __init__( self, - handler: Callable[[httpx.Request], httpx.Response] - | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]], + handler: Callable[[httpx2.Request], httpx2.Response] + | Callable[[httpx2.Request], Coroutine[Any, Any, httpx2.Response]], ) -> None: self.handler = handler @override def handle_request( self, - request: httpx.Request, - ) -> httpx.Response: + request: httpx2.Request, + ) -> httpx2.Response: assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function" assert inspect.isfunction(self.handler), "handler must be a function" return self.handler(request) @@ -90,8 +90,8 @@ def handle_request( @override async def handle_async_request( self, - request: httpx.Request, - ) -> httpx.Response: + request: httpx2.Request, + ) -> httpx2.Response: assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function" return await self.handler(request) @@ -117,7 +117,7 @@ async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] def _get_open_connections(client: OpenAI | AsyncOpenAI) -> int: transport = client._client._transport - if isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport): + if isinstance(transport, httpx2.HTTPTransport) or isinstance(transport, httpx2.AsyncHTTPTransport): return len(transport._pool._requests) assert type(transport).__module__ == "httpx2" @@ -125,24 +125,24 @@ def _get_open_connections(client: OpenAI | AsyncOpenAI) -> int: class TestOpenAI: - @pytest.mark.respx(base_url=base_url) - def test_raw_response(self, respx_mock: MockRouter, client: OpenAI) -> None: - respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + def test_raw_response(self, respx2_mock: MockRouter, client: OpenAI) -> None: + respx2_mock.post("/foo").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) - response = client.post("/foo", cast_to=httpx.Response) + response = client.post("/foo", cast_to=httpx2.Response) assert response.status_code == 200 - assert type(response).__module__ == os.environ.get("OPENAI_TEST_HTTP_CLIENT", "httpx") + assert type(response).__module__ == os.environ.get("OPENAI_TEST_HTTP_CLIENT", "httpx2") assert response.json() == {"foo": "bar"} - @pytest.mark.respx(base_url=base_url) - def test_raw_response_for_binary(self, respx_mock: MockRouter, client: OpenAI) -> None: - respx_mock.post("/foo").mock( - return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') + @pytest.mark.respx2(base_url=base_url) + def test_raw_response_for_binary(self, respx2_mock: MockRouter, client: OpenAI) -> None: + respx2_mock.post("/foo").mock( + return_value=httpx2.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') ) - response = client.post("/foo", cast_to=httpx.Response) + response = client.post("/foo", cast_to=httpx2.Response) assert response.status_code == 200 - assert type(response).__module__ == os.environ.get("OPENAI_TEST_HTTP_CLIENT", "httpx") + assert type(response).__module__ == os.environ.get("OPENAI_TEST_HTTP_CLIENT", "httpx2") assert response.json() == {"foo": "bar"} def test_copy(self, client: OpenAI) -> None: @@ -168,10 +168,10 @@ def test_copy_default_options(self, client: OpenAI) -> None: assert copied.max_retries == 7 # timeout - assert isinstance(client.timeout, httpx.Timeout) + assert isinstance(client.timeout, httpx2.Timeout) copied = client.copy(timeout=None) assert copied.timeout is None - assert isinstance(client.timeout, httpx.Timeout) + assert isinstance(client.timeout, httpx2.Timeout) def test_copy_default_headers(self) -> None: client = OpenAI( @@ -335,12 +335,12 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic def test_request_timeout(self, client: OpenAI) -> None: request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + timeout = httpx2.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT - request = client._build_request(FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == httpx.Timeout(100.0) + request = client._build_request(FinalRequestOptions(method="get", url="/foo", timeout=httpx2.Timeout(100.0))) + timeout = httpx2.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == httpx2.Timeout(100.0) def test_client_timeout_option(self) -> None: client = OpenAI( @@ -348,18 +348,18 @@ def test_client_timeout_option(self) -> None: api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True, - timeout=httpx.Timeout(0), + timeout=httpx2.Timeout(0), ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == httpx.Timeout(0) + timeout = httpx2.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == httpx2.Timeout(0) client.close() def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used - with httpx.Client(timeout=None) as http_client: + with httpx2.Client(timeout=None) as http_client: client = OpenAI( base_url=base_url, api_key=api_key, @@ -369,13 +369,13 @@ def test_http_client_timeout_option(self) -> None: ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == httpx.Timeout(None) + timeout = httpx2.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == httpx2.Timeout(None) client.close() # no timeout given to the httpx client should not use the httpx default - with httpx.Client() as http_client: + with httpx2.Client() as http_client: client = OpenAI( base_url=base_url, api_key=api_key, @@ -385,13 +385,13 @@ def test_http_client_timeout_option(self) -> None: ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + timeout = httpx2.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT client.close() # explicitly passing the default timeout currently results in it being ignored - with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: + with httpx2.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = OpenAI( base_url=base_url, api_key=api_key, @@ -401,14 +401,14 @@ def test_http_client_timeout_option(self) -> None: ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + timeout = httpx2.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT # our default client.close() async def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): - async with httpx.AsyncClient() as http_client: + async with httpx2.AsyncClient() as http_client: OpenAI( base_url=base_url, api_key=api_key, @@ -524,9 +524,9 @@ def test_validate_headers(self) -> None: with pytest.raises(OpenAIError, match="Missing credentials"): OpenAI(base_url=base_url, api_key=None, admin_api_key=None, _strict_response_validation=True) - @pytest.mark.respx(base_url=base_url) - def test_api_key_provider_preserves_admin_auth(self, respx_mock: MockRouter) -> None: - respx_mock.get("/organization/projects").mock(return_value=httpx.Response(200, json={"ok": True})) + @pytest.mark.respx2(base_url=base_url) + def test_api_key_provider_preserves_admin_auth(self, respx2_mock: MockRouter) -> None: + respx2_mock.get("/organization/projects").mock(return_value=httpx2.Response(200, json={"ok": True})) provider_called = False @@ -538,7 +538,7 @@ def api_key_provider() -> str: client = OpenAI(base_url=base_url, api_key=api_key_provider, admin_api_key=admin_api_key) response = client.get( "/organization/projects", - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"security": {"admin_api_key_auth": True}}, ) @@ -558,20 +558,20 @@ def api_key_provider() -> str: with pytest.raises(TypeError, match="Could not resolve authentication method"): client.get( "/organization/projects", - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"security": {"admin_api_key_auth": True}}, ) assert provider_called is False - @pytest.mark.respx(base_url=base_url) - def test_workload_identity_preserves_admin_auth(self, respx_mock: MockRouter) -> None: - respx_mock.get("/organization/projects").mock(return_value=httpx.Response(200, json={"ok": True})) + @pytest.mark.respx2(base_url=base_url) + def test_workload_identity_preserves_admin_auth(self, respx2_mock: MockRouter) -> None: + respx2_mock.get("/organization/projects").mock(return_value=httpx2.Response(200, json={"ok": True})) client = OpenAI(base_url=base_url, workload_identity=workload_identity, admin_api_key=admin_api_key) response = client.get( "/organization/projects", - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"security": {"admin_api_key_auth": True}}, ) @@ -599,7 +599,7 @@ def test_default_query_option(self) -> None: default_query={"query_param": "bar"}, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - url = httpx.URL(request.url) + url = httpx2.URL(request.url) assert dict(url.params) == {"query_param": "bar"} request = client._build_request( @@ -609,14 +609,14 @@ def test_default_query_option(self) -> None: params={"foo": "baz", "query_param": "overridden"}, ) ) - url = httpx.URL(request.url) + url = httpx2.URL(request.url) assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} client.close() def test_hardcoded_query_params_in_url(self, client: OpenAI) -> None: request = client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) - url = httpx.URL(str(request.url)) + url = httpx2.URL(str(request.url)) assert dict(url.params) == {"beta": "true"} request = client._build_request( @@ -626,7 +626,7 @@ def test_hardcoded_query_params_in_url(self, client: OpenAI) -> None: params={"limit": "10", "page": "abc"}, ) ) - url = httpx.URL(str(request.url)) + url = httpx2.URL(str(request.url)) assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} request = client._build_request( @@ -764,16 +764,16 @@ def test_multipart_repeating_array(self, client: OpenAI) -> None: b"", ] - @pytest.mark.respx(base_url=base_url) - def test_binary_content_upload(self, respx_mock: MockRouter, client: OpenAI) -> None: - respx_mock.post("/upload").mock(side_effect=mirror_request_content) + @pytest.mark.respx2(base_url=base_url) + def test_binary_content_upload(self, respx2_mock: MockRouter, client: OpenAI) -> None: + respx2_mock.post("/upload").mock(side_effect=mirror_request_content) file_content = b"Hello, this is a test file." response = client.post( "/upload", content=file_content, - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"headers": {"Content-Type": "application/octet-stream"}}, ) @@ -786,21 +786,21 @@ def test_binary_content_upload_with_iterator(self) -> None: counter = Counter() iterator = _make_sync_iterator([file_content], counter=counter) - def mock_handler(request: httpx.Request) -> httpx.Response: + def mock_handler(request: httpx2.Request) -> httpx2.Response: assert counter.value == 0, "the request body should not have been read" - return httpx.Response(200, content=request.read()) + return httpx2.Response(200, content=request.read()) with OpenAI( base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True, - http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), + http_client=httpx2.Client(transport=MockTransport(handler=mock_handler)), ) as client: response = client.post( "/upload", content=iterator, - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"headers": {"Content-Type": "application/octet-stream"}}, ) @@ -809,9 +809,9 @@ def mock_handler(request: httpx.Request) -> httpx.Response: assert response.content == file_content assert counter.value == 1 - @pytest.mark.respx(base_url=base_url) - def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: OpenAI) -> None: - respx_mock.post("/upload").mock(side_effect=mirror_request_content) + @pytest.mark.respx2(base_url=base_url) + def test_binary_content_upload_with_body_is_deprecated(self, respx2_mock: MockRouter, client: OpenAI) -> None: + respx2_mock.post("/upload").mock(side_effect=mirror_request_content) file_content = b"Hello, this is a test file." @@ -821,7 +821,7 @@ def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRou response = client.post( "/upload", body=file_content, - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"headers": {"Content-Type": "application/octet-stream"}}, ) @@ -829,22 +829,22 @@ def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRou assert response.request.headers["Content-Type"] == "application/octet-stream" assert response.content == file_content - @pytest.mark.respx(base_url=base_url) - def test_basic_union_response(self, respx_mock: MockRouter, client: OpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + def test_basic_union_response(self, respx2_mock: MockRouter, client: OpenAI) -> None: class Model1(BaseModel): name: str class Model2(BaseModel): foo: str - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + respx2_mock.get("/foo").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" - @pytest.mark.respx(base_url=base_url) - def test_union_response_different_types(self, respx_mock: MockRouter, client: OpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + def test_union_response_different_types(self, respx2_mock: MockRouter, client: OpenAI) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -853,20 +853,20 @@ class Model1(BaseModel): class Model2(BaseModel): foo: str - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + respx2_mock.get("/foo").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) + respx2_mock.get("/foo").mock(return_value=httpx2.Response(200, json={"foo": 1})) response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model1) assert response.foo == 1 - @pytest.mark.respx(base_url=base_url) - def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter, client: OpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + def test_non_application_json_content_type_for_json_data(self, respx2_mock: MockRouter, client: OpenAI) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -874,8 +874,8 @@ def test_non_application_json_content_type_for_json_data(self, respx_mock: MockR class Model(BaseModel): foo: int - respx_mock.get("/foo").mock( - return_value=httpx.Response( + respx2_mock.get("/foo").mock( + return_value=httpx2.Response( 200, content=json.dumps({"foo": 2}), headers={"Content-Type": "application/text"}, @@ -920,7 +920,7 @@ def test_base_url_env(self) -> None: api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True, - http_client=httpx.Client(), + http_client=httpx2.Client(), ), ], ids=["standard", "custom http client"], @@ -950,7 +950,7 @@ def test_base_url_trailing_slash(self, client: OpenAI) -> None: api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True, - http_client=httpx.Client(), + http_client=httpx2.Client(), ), ], ids=["standard", "custom http client"], @@ -980,7 +980,7 @@ def test_base_url_no_trailing_slash(self, client: OpenAI) -> None: api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True, - http_client=httpx.Client(), + http_client=httpx2.Client(), ), ], ids=["standard", "custom http client"], @@ -1019,12 +1019,12 @@ def test_client_context_manager(self) -> None: assert not test_client.is_closed() assert test_client.is_closed() - @pytest.mark.respx(base_url=base_url) - def test_client_response_validation_error(self, respx_mock: MockRouter, client: OpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + def test_client_response_validation_error(self, respx2_mock: MockRouter, client: OpenAI) -> None: class Model(BaseModel): foo: str - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}})) + respx2_mock.get("/foo").mock(return_value=httpx2.Response(200, json={"foo": {"invalid": True}})) with pytest.raises(APIResponseValidationError) as exc: client.get("/foo", cast_to=Model) @@ -1041,23 +1041,23 @@ def test_client_max_retries_validation(self) -> None: max_retries=cast(Any, None), ) - @pytest.mark.respx(base_url=base_url) - def test_default_stream_cls(self, respx_mock: MockRouter, client: OpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + def test_default_stream_cls(self, respx2_mock: MockRouter, client: OpenAI) -> None: class Model(BaseModel): name: str - respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + respx2_mock.post("/foo").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) stream = client.post("/foo", cast_to=Model, stream=True, stream_cls=Stream[Model]) assert isinstance(stream, Stream) stream.response.close() - @pytest.mark.respx(base_url=base_url) - def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: + @pytest.mark.respx2(base_url=base_url) + def test_received_text_for_expected_json(self, respx2_mock: MockRouter) -> None: class Model(BaseModel): name: str - respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) + respx2_mock.get("/foo").mock(return_value=httpx2.Response(200, text="my-custom-format")) strict_client = OpenAI( base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True @@ -1108,7 +1108,7 @@ class Model(BaseModel): def test_parse_retry_after_header( self, remaining_retries: int, retry_after: str, timeout: float, client: OpenAI ) -> None: - headers = httpx.Headers({"retry-after": retry_after}) + headers = httpx2.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) calculated = client._calculate_retry_timeout(remaining_retries, options, headers) assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] @@ -1126,24 +1126,24 @@ def test_parse_retry_after_header( ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) def test_retry_after_max_delay(self, headers: dict[str, str], should_retry: bool, client: OpenAI) -> None: - response = httpx.Response(429, headers=headers) + response = httpx2.Response(429, headers=headers) assert client._should_retry(response) is should_retry - @pytest.mark.respx(base_url=base_url) - def test_does_not_retry_retry_after_above_max(self, respx_mock: MockRouter, client: OpenAI) -> None: - route = respx_mock.get("/foo").mock( - return_value=httpx.Response(429, headers={"retry-after": "121"}, json={"error": {}}) + @pytest.mark.respx2(base_url=base_url) + def test_does_not_retry_retry_after_above_max(self, respx2_mock: MockRouter, client: OpenAI) -> None: + route = respx2_mock.get("/foo").mock( + return_value=httpx2.Response(429, headers={"retry-after": "121"}, json={"error": {}}) ) with pytest.raises(APIStatusError): - client.get("/foo", cast_to=httpx.Response) + client.get("/foo", cast_to=httpx2.Response) assert route.call_count == 1 - @pytest.mark.respx(base_url=base_url) - def test_invalid_retry_after_date_does_not_mask_status_error(self, respx_mock: MockRouter, client: OpenAI) -> None: - route = respx_mock.get("/foo").mock( - return_value=httpx.Response( + @pytest.mark.respx2(base_url=base_url) + def test_invalid_retry_after_date_does_not_mask_status_error(self, respx2_mock: MockRouter, client: OpenAI) -> None: + route = respx2_mock.get("/foo").mock( + return_value=httpx2.Response( 400, headers={"retry-after": "Fri, 29 Sep 100000 16:26:57 GMT"}, json={"error": {}}, @@ -1151,14 +1151,14 @@ def test_invalid_retry_after_date_does_not_mask_status_error(self, respx_mock: M ) with pytest.raises(APIStatusError): - client.get("/foo", cast_to=httpx.Response) + client.get("/foo", cast_to=httpx2.Response) assert route.call_count == 1 @mock.patch("openai._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, client: OpenAI) -> None: - respx_mock.post("/chat/completions").mock(side_effect=httpx.TimeoutException("Test timeout error")) + @pytest.mark.respx2(base_url=base_url) + def test_retrying_timeout_errors_doesnt_leak(self, respx2_mock: MockRouter, client: OpenAI) -> None: + respx2_mock.post("/chat/completions").mock(side_effect=httpx2.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): client.chat.completions.with_streaming_response.create( @@ -1174,9 +1174,9 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, clien assert _get_open_connections(client) == 0 @mock.patch("openai._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, client: OpenAI) -> None: - respx_mock.post("/chat/completions").mock(return_value=httpx.Response(500)) + @pytest.mark.respx2(base_url=base_url) + def test_retrying_status_errors_doesnt_leak(self, respx2_mock: MockRouter, client: OpenAI) -> None: + respx2_mock.post("/chat/completions").mock(return_value=httpx2.Response(500)) with pytest.raises(APIStatusError): client.chat.completions.with_streaming_response.create( @@ -1192,29 +1192,29 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) @pytest.mark.parametrize("failure_mode", ["status", "exception"]) def test_retries_taken( self, client: OpenAI, failures_before_success: int, failure_mode: Literal["status", "exception"], - respx_mock: MockRouter, + respx2_mock: MockRouter, ) -> None: client = client.with_options(max_retries=4) nb_retries = 0 - def retry_handler(_request: httpx.Request) -> httpx.Response: + def retry_handler(_request: httpx2.Request) -> httpx2.Response: nonlocal nb_retries if nb_retries < failures_before_success: nb_retries += 1 if failure_mode == "exception": raise RuntimeError("oops") - return httpx.Response(500) - return httpx.Response(200) + return httpx2.Response(500) + return httpx2.Response(200) - respx_mock.post("/chat/completions").mock(side_effect=retry_handler) + respx2_mock.post("/chat/completions").mock(side_effect=retry_handler) response = client.chat.completions.with_raw_response.create( messages=[ @@ -1231,22 +1231,22 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) def test_omit_retry_count_header( - self, client: OpenAI, failures_before_success: int, respx_mock: MockRouter + self, client: OpenAI, failures_before_success: int, respx2_mock: MockRouter ) -> None: client = client.with_options(max_retries=4) nb_retries = 0 - def retry_handler(_request: httpx.Request) -> httpx.Response: + def retry_handler(_request: httpx2.Request) -> httpx2.Response: nonlocal nb_retries if nb_retries < failures_before_success: nb_retries += 1 - return httpx.Response(500) - return httpx.Response(200) + return httpx2.Response(500) + return httpx2.Response(200) - respx_mock.post("/chat/completions").mock(side_effect=retry_handler) + respx2_mock.post("/chat/completions").mock(side_effect=retry_handler) response = client.chat.completions.with_raw_response.create( messages=[ @@ -1263,22 +1263,22 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) def test_overwrite_retry_count_header( - self, client: OpenAI, failures_before_success: int, respx_mock: MockRouter + self, client: OpenAI, failures_before_success: int, respx2_mock: MockRouter ) -> None: client = client.with_options(max_retries=4) nb_retries = 0 - def retry_handler(_request: httpx.Request) -> httpx.Response: + def retry_handler(_request: httpx2.Request) -> httpx2.Response: nonlocal nb_retries if nb_retries < failures_before_success: nb_retries += 1 - return httpx.Response(500) - return httpx.Response(200) + return httpx2.Response(500) + return httpx2.Response(200) - respx_mock.post("/chat/completions").mock(side_effect=retry_handler) + respx2_mock.post("/chat/completions").mock(side_effect=retry_handler) response = client.chat.completions.with_raw_response.create( messages=[ @@ -1295,22 +1295,22 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) def test_retries_taken_new_response_class( - self, client: OpenAI, failures_before_success: int, respx_mock: MockRouter + self, client: OpenAI, failures_before_success: int, respx2_mock: MockRouter ) -> None: client = client.with_options(max_retries=4) nb_retries = 0 - def retry_handler(_request: httpx.Request) -> httpx.Response: + def retry_handler(_request: httpx2.Request) -> httpx2.Response: nonlocal nb_retries if nb_retries < failures_before_success: nb_retries += 1 - return httpx.Response(500) - return httpx.Response(200) + return httpx2.Response(500) + return httpx2.Response(200) - respx_mock.post("/chat/completions").mock(side_effect=retry_handler) + respx2_mock.post("/chat/completions").mock(side_effect=retry_handler) with client.chat.completions.with_streaming_response.create( messages=[ @@ -1351,30 +1351,32 @@ def test_default_client_creation(self) -> None: trust_env=True, http1=True, http2=False, - limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + limits=httpx2.Limits(max_connections=100, max_keepalive_connections=20), ) - @pytest.mark.respx(base_url=base_url) - def test_follow_redirects(self, respx_mock: MockRouter, client: OpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + def test_follow_redirects(self, respx2_mock: MockRouter, client: OpenAI) -> None: # Test that the default follow_redirects=True allows following redirects - respx_mock.post("/redirect").mock( - return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + respx2_mock.post("/redirect").mock( + return_value=httpx2.Response(302, headers={"Location": f"{base_url}/redirected"}) ) - respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + respx2_mock.get("/redirected").mock(return_value=httpx2.Response(200, json={"status": "ok"})) - response = client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + response = client.post("/redirect", body={"key": "value"}, cast_to=httpx2.Response) assert response.status_code == 200 assert response.json() == {"status": "ok"} - @pytest.mark.respx(base_url=base_url) - def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: OpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + def test_follow_redirects_disabled(self, respx2_mock: MockRouter, client: OpenAI) -> None: # Test that follow_redirects=False prevents following redirects - respx_mock.post("/redirect").mock( - return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + respx2_mock.post("/redirect").mock( + return_value=httpx2.Response(302, headers={"Location": f"{base_url}/redirected"}) ) with pytest.raises(APIStatusError) as exc_info: - client.post("/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response) + client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx2.Response + ) assert exc_info.value.response.status_code == 302 assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" @@ -1398,12 +1400,12 @@ def test_api_key_before_after_refresh_str(self) -> None: assert client.auth_headers.get("Authorization") == "Bearer test_api_key" - @pytest.mark.respx() - def test_api_key_refresh_on_retry(self, respx_mock: MockRouter) -> None: - respx_mock.post(base_url + "/chat/completions").mock( + @pytest.mark.respx2() + def test_api_key_refresh_on_retry(self, respx2_mock: MockRouter) -> None: + respx2_mock.post(base_url + "/chat/completions").mock( side_effect=[ - httpx.Response(500, json={"error": "server error"}), - httpx.Response(200, json={"foo": "bar"}), + httpx2.Response(500, json={"error": "server error"}), + httpx2.Response(200, json={"foo": "bar"}), ] ) @@ -1422,7 +1424,7 @@ def token_provider() -> str: client = OpenAI(base_url=base_url, api_key=token_provider) client.chat.completions.create(messages=[], model="gpt-4") - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert len(calls) == 2 assert calls[0].request.headers.get("Authorization") == "Bearer first" @@ -1437,24 +1439,24 @@ def test_copy_auth(self) -> None: class TestAsyncOpenAI: - @pytest.mark.respx(base_url=base_url) - async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: - respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + @pytest.mark.respx2(base_url=base_url) + async def test_raw_response(self, respx2_mock: MockRouter, async_client: AsyncOpenAI) -> None: + respx2_mock.post("/foo").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) - response = await async_client.post("/foo", cast_to=httpx.Response) + response = await async_client.post("/foo", cast_to=httpx2.Response) assert response.status_code == 200 - assert type(response).__module__ == os.environ.get("OPENAI_TEST_HTTP_CLIENT", "httpx") + assert type(response).__module__ == os.environ.get("OPENAI_TEST_HTTP_CLIENT", "httpx2") assert response.json() == {"foo": "bar"} - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_for_binary(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: - respx_mock.post("/foo").mock( - return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') + @pytest.mark.respx2(base_url=base_url) + async def test_raw_response_for_binary(self, respx2_mock: MockRouter, async_client: AsyncOpenAI) -> None: + respx2_mock.post("/foo").mock( + return_value=httpx2.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') ) - response = await async_client.post("/foo", cast_to=httpx.Response) + response = await async_client.post("/foo", cast_to=httpx2.Response) assert response.status_code == 200 - assert type(response).__module__ == os.environ.get("OPENAI_TEST_HTTP_CLIENT", "httpx") + assert type(response).__module__ == os.environ.get("OPENAI_TEST_HTTP_CLIENT", "httpx2") assert response.json() == {"foo": "bar"} def test_copy(self, async_client: AsyncOpenAI) -> None: @@ -1480,10 +1482,10 @@ def test_copy_default_options(self, async_client: AsyncOpenAI) -> None: assert copied.max_retries == 7 # timeout - assert isinstance(async_client.timeout, httpx.Timeout) + assert isinstance(async_client.timeout, httpx2.Timeout) copied = async_client.copy(timeout=None) assert copied.timeout is None - assert isinstance(async_client.timeout, httpx.Timeout) + assert isinstance(async_client.timeout, httpx2.Timeout) async def test_copy_default_headers(self) -> None: client = AsyncOpenAI( @@ -1647,14 +1649,14 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic async def test_request_timeout(self, async_client: AsyncOpenAI) -> None: request = async_client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + timeout = httpx2.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT request = async_client._build_request( - FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)) + FinalRequestOptions(method="get", url="/foo", timeout=httpx2.Timeout(100.0)) ) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == httpx.Timeout(100.0) + timeout = httpx2.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == httpx2.Timeout(100.0) async def test_client_timeout_option(self) -> None: client = AsyncOpenAI( @@ -1662,18 +1664,18 @@ async def test_client_timeout_option(self) -> None: api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True, - timeout=httpx.Timeout(0), + timeout=httpx2.Timeout(0), ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == httpx.Timeout(0) + timeout = httpx2.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == httpx2.Timeout(0) await client.close() async def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used - async with httpx.AsyncClient(timeout=None) as http_client: + async with httpx2.AsyncClient(timeout=None) as http_client: client = AsyncOpenAI( base_url=base_url, api_key=api_key, @@ -1683,13 +1685,13 @@ async def test_http_client_timeout_option(self) -> None: ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore - assert timeout == httpx.Timeout(None) + timeout = httpx2.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == httpx2.Timeout(None) await client.close() # no timeout given to the httpx client should not use the httpx default - async with httpx.AsyncClient() as http_client: + async with httpx2.AsyncClient() as http_client: client = AsyncOpenAI( base_url=base_url, api_key=api_key, @@ -1699,13 +1701,13 @@ async def test_http_client_timeout_option(self) -> None: ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + timeout = httpx2.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT await client.close() # explicitly passing the default timeout currently results in it being ignored - async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: + async with httpx2.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = AsyncOpenAI( base_url=base_url, api_key=api_key, @@ -1715,14 +1717,14 @@ async def test_http_client_timeout_option(self) -> None: ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + timeout = httpx2.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT # our default await client.close() def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): - with httpx.Client() as http_client: + with httpx2.Client() as http_client: AsyncOpenAI( base_url=base_url, api_key=api_key, @@ -1837,9 +1839,9 @@ async def test_validate_headers(self) -> None: with pytest.raises(OpenAIError, match="Missing credentials"): AsyncOpenAI(base_url=base_url, api_key=None, admin_api_key=None, _strict_response_validation=True) - @pytest.mark.respx(base_url=base_url) - async def test_api_key_provider_preserves_admin_auth(self, respx_mock: MockRouter) -> None: - respx_mock.get("/organization/projects").mock(return_value=httpx.Response(200, json={"ok": True})) + @pytest.mark.respx2(base_url=base_url) + async def test_api_key_provider_preserves_admin_auth(self, respx2_mock: MockRouter) -> None: + respx2_mock.get("/organization/projects").mock(return_value=httpx2.Response(200, json={"ok": True})) provider_called = False @@ -1851,7 +1853,7 @@ async def api_key_provider() -> str: client = AsyncOpenAI(base_url=base_url, api_key=api_key_provider, admin_api_key=admin_api_key) response = await client.get( "/organization/projects", - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"security": {"admin_api_key_auth": True}}, ) @@ -1871,20 +1873,20 @@ async def api_key_provider() -> str: with pytest.raises(TypeError, match="Could not resolve authentication method"): await client.get( "/organization/projects", - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"security": {"admin_api_key_auth": True}}, ) assert provider_called is False - @pytest.mark.respx(base_url=base_url) - async def test_workload_identity_preserves_admin_auth(self, respx_mock: MockRouter) -> None: - respx_mock.get("/organization/projects").mock(return_value=httpx.Response(200, json={"ok": True})) + @pytest.mark.respx2(base_url=base_url) + async def test_workload_identity_preserves_admin_auth(self, respx2_mock: MockRouter) -> None: + respx2_mock.get("/organization/projects").mock(return_value=httpx2.Response(200, json={"ok": True})) client = AsyncOpenAI(base_url=base_url, workload_identity=workload_identity, admin_api_key=admin_api_key) response = await client.get( "/organization/projects", - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"security": {"admin_api_key_auth": True}}, ) @@ -1899,7 +1901,7 @@ async def test_default_query_option(self) -> None: default_query={"query_param": "bar"}, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - url = httpx.URL(request.url) + url = httpx2.URL(request.url) assert dict(url.params) == {"query_param": "bar"} request = client._build_request( @@ -1909,14 +1911,14 @@ async def test_default_query_option(self) -> None: params={"foo": "baz", "query_param": "overridden"}, ) ) - url = httpx.URL(request.url) + url = httpx2.URL(request.url) assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} await client.close() async def test_hardcoded_query_params_in_url(self, async_client: AsyncOpenAI) -> None: request = async_client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) - url = httpx.URL(str(request.url)) + url = httpx2.URL(str(request.url)) assert dict(url.params) == {"beta": "true"} request = async_client._build_request( @@ -1926,7 +1928,7 @@ async def test_hardcoded_query_params_in_url(self, async_client: AsyncOpenAI) -> params={"limit": "10", "page": "abc"}, ) ) - url = httpx.URL(str(request.url)) + url = httpx2.URL(str(request.url)) assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} request = async_client._build_request( @@ -2064,16 +2066,16 @@ def test_multipart_repeating_array(self, async_client: AsyncOpenAI) -> None: b"", ] - @pytest.mark.respx(base_url=base_url) - async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: - respx_mock.post("/upload").mock(side_effect=mirror_request_content) + @pytest.mark.respx2(base_url=base_url) + async def test_binary_content_upload(self, respx2_mock: MockRouter, async_client: AsyncOpenAI) -> None: + respx2_mock.post("/upload").mock(side_effect=mirror_request_content) file_content = b"Hello, this is a test file." response = await async_client.post( "/upload", content=file_content, - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"headers": {"Content-Type": "application/octet-stream"}}, ) @@ -2086,21 +2088,21 @@ async def test_binary_content_upload_with_asynciterator(self) -> None: counter = Counter() iterator = _make_async_iterator([file_content], counter=counter) - async def mock_handler(request: httpx.Request) -> httpx.Response: + async def mock_handler(request: httpx2.Request) -> httpx2.Response: assert counter.value == 0, "the request body should not have been read" - return httpx.Response(200, content=await request.aread()) + return httpx2.Response(200, content=await request.aread()) async with AsyncOpenAI( base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True, - http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), + http_client=httpx2.AsyncClient(transport=MockTransport(handler=mock_handler)), ) as client: response = await client.post( "/upload", content=iterator, - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"headers": {"Content-Type": "application/octet-stream"}}, ) @@ -2109,11 +2111,11 @@ async def mock_handler(request: httpx.Request) -> httpx.Response: assert response.content == file_content assert counter.value == 1 - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_binary_content_upload_with_body_is_deprecated( - self, respx_mock: MockRouter, async_client: AsyncOpenAI + self, respx2_mock: MockRouter, async_client: AsyncOpenAI ) -> None: - respx_mock.post("/upload").mock(side_effect=mirror_request_content) + respx2_mock.post("/upload").mock(side_effect=mirror_request_content) file_content = b"Hello, this is a test file." @@ -2123,7 +2125,7 @@ async def test_binary_content_upload_with_body_is_deprecated( response = await async_client.post( "/upload", body=file_content, - cast_to=httpx.Response, + cast_to=httpx2.Response, options={"headers": {"Content-Type": "application/octet-stream"}}, ) @@ -2131,22 +2133,22 @@ async def test_binary_content_upload_with_body_is_deprecated( assert response.request.headers["Content-Type"] == "application/octet-stream" assert response.content == file_content - @pytest.mark.respx(base_url=base_url) - async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + async def test_basic_union_response(self, respx2_mock: MockRouter, async_client: AsyncOpenAI) -> None: class Model1(BaseModel): name: str class Model2(BaseModel): foo: str - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + respx2_mock.get("/foo").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" - @pytest.mark.respx(base_url=base_url) - async def test_union_response_different_types(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + async def test_union_response_different_types(self, respx2_mock: MockRouter, async_client: AsyncOpenAI) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -2155,21 +2157,21 @@ class Model1(BaseModel): class Model2(BaseModel): foo: str - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + respx2_mock.get("/foo").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) + respx2_mock.get("/foo").mock(return_value=httpx2.Response(200, json={"foo": 1})) response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model1) assert response.foo == 1 - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_non_application_json_content_type_for_json_data( - self, respx_mock: MockRouter, async_client: AsyncOpenAI + self, respx2_mock: MockRouter, async_client: AsyncOpenAI ) -> None: """ Response that sets Content-Type to something other than application/json but returns json data @@ -2178,8 +2180,8 @@ async def test_non_application_json_content_type_for_json_data( class Model(BaseModel): foo: int - respx_mock.get("/foo").mock( - return_value=httpx.Response( + respx2_mock.get("/foo").mock( + return_value=httpx2.Response( 200, content=json.dumps({"foo": 2}), headers={"Content-Type": "application/text"}, @@ -2224,7 +2226,7 @@ async def test_base_url_env(self) -> None: api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True, - http_client=httpx.AsyncClient(), + http_client=httpx2.AsyncClient(), ), ], ids=["standard", "custom http client"], @@ -2254,7 +2256,7 @@ async def test_base_url_trailing_slash(self, client: AsyncOpenAI) -> None: api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True, - http_client=httpx.AsyncClient(), + http_client=httpx2.AsyncClient(), ), ], ids=["standard", "custom http client"], @@ -2284,7 +2286,7 @@ async def test_base_url_no_trailing_slash(self, client: AsyncOpenAI) -> None: api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True, - http_client=httpx.AsyncClient(), + http_client=httpx2.AsyncClient(), ), ], ids=["standard", "custom http client"], @@ -2324,12 +2326,12 @@ async def test_client_context_manager(self) -> None: assert not test_client.is_closed() assert test_client.is_closed() - @pytest.mark.respx(base_url=base_url) - async def test_client_response_validation_error(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + async def test_client_response_validation_error(self, respx2_mock: MockRouter, async_client: AsyncOpenAI) -> None: class Model(BaseModel): foo: str - respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}})) + respx2_mock.get("/foo").mock(return_value=httpx2.Response(200, json={"foo": {"invalid": True}})) with pytest.raises(APIResponseValidationError) as exc: await async_client.get("/foo", cast_to=Model) @@ -2346,23 +2348,23 @@ async def test_client_max_retries_validation(self) -> None: max_retries=cast(Any, None), ) - @pytest.mark.respx(base_url=base_url) - async def test_default_stream_cls(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + async def test_default_stream_cls(self, respx2_mock: MockRouter, async_client: AsyncOpenAI) -> None: class Model(BaseModel): name: str - respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + respx2_mock.post("/foo").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) stream = await async_client.post("/foo", cast_to=Model, stream=True, stream_cls=AsyncStream[Model]) assert isinstance(stream, AsyncStream) await stream.response.aclose() - @pytest.mark.respx(base_url=base_url) - async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: + @pytest.mark.respx2(base_url=base_url) + async def test_received_text_for_expected_json(self, respx2_mock: MockRouter) -> None: class Model(BaseModel): name: str - respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) + respx2_mock.get("/foo").mock(return_value=httpx2.Response(200, text="my-custom-format")) strict_client = AsyncOpenAI( base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True @@ -2413,30 +2415,30 @@ class Model(BaseModel): async def test_parse_retry_after_header( self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncOpenAI ) -> None: - headers = httpx.Headers({"retry-after": retry_after}) + headers = httpx2.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) calculated = async_client._calculate_retry_timeout(remaining_retries, options, headers) assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_does_not_retry_retry_after_above_max( - self, respx_mock: MockRouter, async_client: AsyncOpenAI + self, respx2_mock: MockRouter, async_client: AsyncOpenAI ) -> None: - route = respx_mock.get("/foo").mock( - return_value=httpx.Response(429, headers={"retry-after": "121"}, json={"error": {}}) + route = respx2_mock.get("/foo").mock( + return_value=httpx2.Response(429, headers={"retry-after": "121"}, json={"error": {}}) ) with pytest.raises(APIStatusError): - await async_client.get("/foo", cast_to=httpx.Response) + await async_client.get("/foo", cast_to=httpx2.Response) assert route.call_count == 1 - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_invalid_retry_after_date_does_not_mask_status_error( - self, respx_mock: MockRouter, async_client: AsyncOpenAI + self, respx2_mock: MockRouter, async_client: AsyncOpenAI ) -> None: - route = respx_mock.get("/foo").mock( - return_value=httpx.Response( + route = respx2_mock.get("/foo").mock( + return_value=httpx2.Response( 400, headers={"retry-after": "Fri, 29 Sep 100000 16:26:57 GMT"}, json={"error": {}}, @@ -2444,14 +2446,16 @@ async def test_invalid_retry_after_date_does_not_mask_status_error( ) with pytest.raises(APIStatusError): - await async_client.get("/foo", cast_to=httpx.Response) + await async_client.get("/foo", cast_to=httpx2.Response) assert route.call_count == 1 @mock.patch("openai._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, async_client: AsyncOpenAI) -> None: - respx_mock.post("/chat/completions").mock(side_effect=httpx.TimeoutException("Test timeout error")) + @pytest.mark.respx2(base_url=base_url) + async def test_retrying_timeout_errors_doesnt_leak( + self, respx2_mock: MockRouter, async_client: AsyncOpenAI + ) -> None: + respx2_mock.post("/chat/completions").mock(side_effect=httpx2.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): await async_client.chat.completions.with_streaming_response.create( @@ -2467,9 +2471,9 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, assert _get_open_connections(async_client) == 0 @mock.patch("openai._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, async_client: AsyncOpenAI) -> None: - respx_mock.post("/chat/completions").mock(return_value=httpx.Response(500)) + @pytest.mark.respx2(base_url=base_url) + async def test_retrying_status_errors_doesnt_leak(self, respx2_mock: MockRouter, async_client: AsyncOpenAI) -> None: + respx2_mock.post("/chat/completions").mock(return_value=httpx2.Response(500)) with pytest.raises(APIStatusError): await async_client.chat.completions.with_streaming_response.create( @@ -2485,29 +2489,29 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) @pytest.mark.parametrize("failure_mode", ["status", "exception"]) async def test_retries_taken( self, async_client: AsyncOpenAI, failures_before_success: int, failure_mode: Literal["status", "exception"], - respx_mock: MockRouter, + respx2_mock: MockRouter, ) -> None: client = async_client.with_options(max_retries=4) nb_retries = 0 - def retry_handler(_request: httpx.Request) -> httpx.Response: + def retry_handler(_request: httpx2.Request) -> httpx2.Response: nonlocal nb_retries if nb_retries < failures_before_success: nb_retries += 1 if failure_mode == "exception": raise RuntimeError("oops") - return httpx.Response(500) - return httpx.Response(200) + return httpx2.Response(500) + return httpx2.Response(200) - respx_mock.post("/chat/completions").mock(side_effect=retry_handler) + respx2_mock.post("/chat/completions").mock(side_effect=retry_handler) response = await client.chat.completions.with_raw_response.create( messages=[ @@ -2524,22 +2528,22 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_omit_retry_count_header( - self, async_client: AsyncOpenAI, failures_before_success: int, respx_mock: MockRouter + self, async_client: AsyncOpenAI, failures_before_success: int, respx2_mock: MockRouter ) -> None: client = async_client.with_options(max_retries=4) nb_retries = 0 - def retry_handler(_request: httpx.Request) -> httpx.Response: + def retry_handler(_request: httpx2.Request) -> httpx2.Response: nonlocal nb_retries if nb_retries < failures_before_success: nb_retries += 1 - return httpx.Response(500) - return httpx.Response(200) + return httpx2.Response(500) + return httpx2.Response(200) - respx_mock.post("/chat/completions").mock(side_effect=retry_handler) + respx2_mock.post("/chat/completions").mock(side_effect=retry_handler) response = await client.chat.completions.with_raw_response.create( messages=[ @@ -2556,22 +2560,22 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_overwrite_retry_count_header( - self, async_client: AsyncOpenAI, failures_before_success: int, respx_mock: MockRouter + self, async_client: AsyncOpenAI, failures_before_success: int, respx2_mock: MockRouter ) -> None: client = async_client.with_options(max_retries=4) nb_retries = 0 - def retry_handler(_request: httpx.Request) -> httpx.Response: + def retry_handler(_request: httpx2.Request) -> httpx2.Response: nonlocal nb_retries if nb_retries < failures_before_success: nb_retries += 1 - return httpx.Response(500) - return httpx.Response(200) + return httpx2.Response(500) + return httpx2.Response(200) - respx_mock.post("/chat/completions").mock(side_effect=retry_handler) + respx2_mock.post("/chat/completions").mock(side_effect=retry_handler) response = await client.chat.completions.with_raw_response.create( messages=[ @@ -2588,22 +2592,22 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) - @pytest.mark.respx(base_url=base_url) + @pytest.mark.respx2(base_url=base_url) async def test_retries_taken_new_response_class( - self, async_client: AsyncOpenAI, failures_before_success: int, respx_mock: MockRouter + self, async_client: AsyncOpenAI, failures_before_success: int, respx2_mock: MockRouter ) -> None: client = async_client.with_options(max_retries=4) nb_retries = 0 - def retry_handler(_request: httpx.Request) -> httpx.Response: + def retry_handler(_request: httpx2.Request) -> httpx2.Response: nonlocal nb_retries if nb_retries < failures_before_success: nb_retries += 1 - return httpx.Response(500) - return httpx.Response(200) + return httpx2.Response(500) + return httpx2.Response(200) - respx_mock.post("/chat/completions").mock(side_effect=retry_handler) + respx2_mock.post("/chat/completions").mock(side_effect=retry_handler) async with client.chat.completions.with_streaming_response.create( messages=[ @@ -2648,31 +2652,31 @@ async def test_default_client_creation(self) -> None: trust_env=True, http1=True, http2=False, - limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + limits=httpx2.Limits(max_connections=100, max_keepalive_connections=20), ) - @pytest.mark.respx(base_url=base_url) - async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + async def test_follow_redirects(self, respx2_mock: MockRouter, async_client: AsyncOpenAI) -> None: # Test that the default follow_redirects=True allows following redirects - respx_mock.post("/redirect").mock( - return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + respx2_mock.post("/redirect").mock( + return_value=httpx2.Response(302, headers={"Location": f"{base_url}/redirected"}) ) - respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + respx2_mock.get("/redirected").mock(return_value=httpx2.Response(200, json={"status": "ok"})) - response = await async_client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + response = await async_client.post("/redirect", body={"key": "value"}, cast_to=httpx2.Response) assert response.status_code == 200 assert response.json() == {"status": "ok"} - @pytest.mark.respx(base_url=base_url) - async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: + @pytest.mark.respx2(base_url=base_url) + async def test_follow_redirects_disabled(self, respx2_mock: MockRouter, async_client: AsyncOpenAI) -> None: # Test that follow_redirects=False prevents following redirects - respx_mock.post("/redirect").mock( - return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + respx2_mock.post("/redirect").mock( + return_value=httpx2.Response(302, headers={"Location": f"{base_url}/redirected"}) ) with pytest.raises(APIStatusError) as exc_info: await async_client.post( - "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx2.Response ) assert exc_info.value.response.status_code == 302 @@ -2700,12 +2704,12 @@ async def test_api_key_before_after_refresh_str(self) -> None: assert client.auth_headers.get("Authorization") == "Bearer test_api_key" - @pytest.mark.respx() - async def test_bearer_token_refresh_async(self, respx_mock: MockRouter) -> None: - respx_mock.post(base_url + "/chat/completions").mock( + @pytest.mark.respx2() + async def test_bearer_token_refresh_async(self, respx2_mock: MockRouter) -> None: + respx2_mock.post(base_url + "/chat/completions").mock( side_effect=[ - httpx.Response(500, json={"error": "server error"}), - httpx.Response(200, json={"foo": "bar"}), + httpx2.Response(500, json={"error": "server error"}), + httpx2.Response(200, json={"foo": "bar"}), ] ) @@ -2724,7 +2728,7 @@ async def token_provider() -> str: client = AsyncOpenAI(base_url=base_url, api_key=token_provider) await client.chat.completions.create(messages=[], model="gpt-4") - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert len(calls) == 2 assert calls[0].request.headers.get("Authorization") == "Bearer first" @@ -2743,8 +2747,8 @@ async def token_provider_2() -> str: class TestWorkloadIdentity401Retry: - @pytest.mark.respx() - def test_workload_identity_401_retry(self, respx_mock: MockRouter) -> None: + @pytest.mark.respx2() + def test_workload_identity_401_retry(self, respx2_mock: MockRouter) -> None: provider_call_count = 0 def provider() -> str: @@ -2752,9 +2756,9 @@ def provider() -> str: provider_call_count += 1 return f"external-subject-token-{provider_call_count}" - respx_mock.post("https://auth.openai.com/oauth/token").mock( + respx2_mock.post("https://auth.openai.com/oauth/token").mock( side_effect=[ - httpx.Response( + httpx2.Response( 200, json={ "access_token": "openai-access-token-1", @@ -2763,7 +2767,7 @@ def provider() -> str: "expires_in": 3600, }, ), - httpx.Response( + httpx2.Response( 200, json={ "access_token": "openai-access-token-2", @@ -2775,10 +2779,10 @@ def provider() -> str: ] ) - respx_mock.post(base_url + "/chat/completions").mock( + respx2_mock.post(base_url + "/chat/completions").mock( side_effect=[ - httpx.Response(401, json={"error": {"message": "Unauthorized", "type": "invalid_request_error"}}), - httpx.Response( + httpx2.Response(401, json={"error": {"message": "Unauthorized", "type": "invalid_request_error"}}), + httpx2.Response( 200, json={ "id": "chatcmpl-123", @@ -2806,24 +2810,24 @@ def provider() -> str: ) as client: client.chat.completions.create(messages=[], model="gpt-4") - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert len(calls) == 4 - assert calls[0].request.url == httpx.URL("https://auth.openai.com/oauth/token") - assert calls[1].request.url == httpx.URL(base_url + "/chat/completions") + assert calls[0].request.url == httpx2.URL("https://auth.openai.com/oauth/token") + assert calls[1].request.url == httpx2.URL(base_url + "/chat/completions") assert calls[1].request.headers.get("Authorization") == "Bearer openai-access-token-1" - assert calls[2].request.url == httpx.URL("https://auth.openai.com/oauth/token") + assert calls[2].request.url == httpx2.URL("https://auth.openai.com/oauth/token") - assert calls[3].request.url == httpx.URL(base_url + "/chat/completions") + assert calls[3].request.url == httpx2.URL(base_url + "/chat/completions") assert calls[3].request.headers.get("Authorization") == "Bearer openai-access-token-2" assert provider_call_count == 2 - @pytest.mark.respx() - def test_401_without_workload_identity_no_retry(self, respx_mock: MockRouter) -> None: - respx_mock.post(base_url + "/chat/completions").mock( - return_value=httpx.Response( + @pytest.mark.respx2() + def test_401_without_workload_identity_no_retry(self, respx2_mock: MockRouter) -> None: + respx2_mock.post(base_url + "/chat/completions").mock( + return_value=httpx2.Response( 401, json={"error": {"message": "Unauthorized", "type": "invalid_request_error"}} ) ) @@ -2838,11 +2842,11 @@ def test_401_without_workload_identity_no_retry(self, respx_mock: MockRouter) -> assert exc_info.value.status_code == 401 - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert len(calls) == 1 - @pytest.mark.respx() - def test_non_401_errors_no_retry(self, respx_mock: MockRouter) -> None: + @pytest.mark.respx2() + def test_non_401_errors_no_retry(self, respx2_mock: MockRouter) -> None: provider_call_count = 0 def provider() -> str: @@ -2850,8 +2854,8 @@ def provider() -> str: provider_call_count += 1 return "external-subject-token" - respx_mock.post("https://auth.openai.com/oauth/token").mock( - return_value=httpx.Response( + respx2_mock.post("https://auth.openai.com/oauth/token").mock( + return_value=httpx2.Response( 200, json={ "access_token": "openai-access-token-1", @@ -2862,8 +2866,8 @@ def provider() -> str: ) ) - respx_mock.post(base_url + "/chat/completions").mock( - return_value=httpx.Response(403, json={"error": {"message": "Forbidden", "type": "invalid_request_error"}}) + respx2_mock.post(base_url + "/chat/completions").mock( + return_value=httpx2.Response(403, json={"error": {"message": "Forbidden", "type": "invalid_request_error"}}) ) with OpenAI( @@ -2884,15 +2888,15 @@ def provider() -> str: assert exc_info.value.status_code == 403 - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert len(calls) == 2 assert provider_call_count == 1 class TestAsyncWorkloadIdentity401Retry: - @pytest.mark.respx() - async def test_workload_identity_401_retry(self, respx_mock: MockRouter) -> None: + @pytest.mark.respx2() + async def test_workload_identity_401_retry(self, respx2_mock: MockRouter) -> None: provider_call_count = 0 def provider() -> str: @@ -2900,9 +2904,9 @@ def provider() -> str: provider_call_count += 1 return f"external-subject-token-{provider_call_count}" - respx_mock.post("https://auth.openai.com/oauth/token").mock( + respx2_mock.post("https://auth.openai.com/oauth/token").mock( side_effect=[ - httpx.Response( + httpx2.Response( 200, json={ "access_token": "openai-access-token-1", @@ -2911,7 +2915,7 @@ def provider() -> str: "expires_in": 3600, }, ), - httpx.Response( + httpx2.Response( 200, json={ "access_token": "openai-access-token-2", @@ -2923,10 +2927,10 @@ def provider() -> str: ] ) - respx_mock.post(base_url + "/chat/completions").mock( + respx2_mock.post(base_url + "/chat/completions").mock( side_effect=[ - httpx.Response(401, json={"error": {"message": "Unauthorized", "type": "invalid_request_error"}}), - httpx.Response( + httpx2.Response(401, json={"error": {"message": "Unauthorized", "type": "invalid_request_error"}}), + httpx2.Response( 200, json={ "id": "chatcmpl-123", @@ -2954,24 +2958,24 @@ def provider() -> str: ) as client: await client.chat.completions.create(messages=[], model="gpt-4") - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert len(calls) == 4 - assert calls[0].request.url == httpx.URL("https://auth.openai.com/oauth/token") - assert calls[1].request.url == httpx.URL(base_url + "/chat/completions") + assert calls[0].request.url == httpx2.URL("https://auth.openai.com/oauth/token") + assert calls[1].request.url == httpx2.URL(base_url + "/chat/completions") assert calls[1].request.headers.get("Authorization") == "Bearer openai-access-token-1" - assert calls[2].request.url == httpx.URL("https://auth.openai.com/oauth/token") + assert calls[2].request.url == httpx2.URL("https://auth.openai.com/oauth/token") - assert calls[3].request.url == httpx.URL(base_url + "/chat/completions") + assert calls[3].request.url == httpx2.URL(base_url + "/chat/completions") assert calls[3].request.headers.get("Authorization") == "Bearer openai-access-token-2" assert provider_call_count == 2 - @pytest.mark.respx() - async def test_401_without_workload_identity_no_retry(self, respx_mock: MockRouter) -> None: - respx_mock.post(base_url + "/chat/completions").mock( - return_value=httpx.Response( + @pytest.mark.respx2() + async def test_401_without_workload_identity_no_retry(self, respx2_mock: MockRouter) -> None: + respx2_mock.post(base_url + "/chat/completions").mock( + return_value=httpx2.Response( 401, json={"error": {"message": "Unauthorized", "type": "invalid_request_error"}} ) ) @@ -2986,11 +2990,11 @@ async def test_401_without_workload_identity_no_retry(self, respx_mock: MockRout assert exc_info.value.status_code == 401 - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert len(calls) == 1 - @pytest.mark.respx() - async def test_non_401_errors_no_retry(self, respx_mock: MockRouter) -> None: + @pytest.mark.respx2() + async def test_non_401_errors_no_retry(self, respx2_mock: MockRouter) -> None: provider_call_count = 0 def provider() -> str: @@ -2998,8 +3002,8 @@ def provider() -> str: provider_call_count += 1 return "external-subject-token" - respx_mock.post("https://auth.openai.com/oauth/token").mock( - return_value=httpx.Response( + respx2_mock.post("https://auth.openai.com/oauth/token").mock( + return_value=httpx2.Response( 200, json={ "access_token": "openai-access-token-1", @@ -3010,8 +3014,8 @@ def provider() -> str: ) ) - respx_mock.post(base_url + "/chat/completions").mock( - return_value=httpx.Response(403, json={"error": {"message": "Forbidden", "type": "invalid_request_error"}}) + respx2_mock.post(base_url + "/chat/completions").mock( + return_value=httpx2.Response(403, json={"error": {"message": "Forbidden", "type": "invalid_request_error"}}) ) async with AsyncOpenAI( @@ -3032,7 +3036,7 @@ def provider() -> str: assert exc_info.value.status_code == 403 - calls = cast("list[MockRequestCall]", respx_mock.calls) + calls = cast("list[MockRequestCall]", respx2_mock.calls) assert len(calls) == 2 assert provider_call_count == 1 diff --git a/tests/test_httpx2.py b/tests/test_httpx2.py index 3afd2d68d5..764fc00f0b 100644 --- a/tests/test_httpx2.py +++ b/tests/test_httpx2.py @@ -4,7 +4,7 @@ from typing import Any from typing_extensions import override -import httpx +import httpx2 import pytest import openai @@ -22,25 +22,12 @@ from openai.providers import bedrock from openai._constants import DEFAULT_TIMEOUT -httpx2 = pytest.importorskip("httpx2") - -@pytest.fixture(autouse=True) -def forbid_httpx_execution(monkeypatch: pytest.MonkeyPatch) -> None: - def forbidden(*_args: object, **_kwargs: object) -> None: - raise AssertionError("the experimental path unexpectedly executed an HTTPX client") - - monkeypatch.setattr(httpx.Client, "build_request", forbidden) - monkeypatch.setattr(httpx.Client, "send", forbidden) - monkeypatch.setattr(httpx.AsyncClient, "build_request", forbidden) - monkeypatch.setattr(httpx.AsyncClient, "send", forbidden) - - -def model_list(request: httpx.Request) -> httpx.Response: +def model_list(request: httpx2.Request) -> httpx2.Response: return httpx2.Response(200, json={"object": "list", "data": []}, request=request) -def sse_response(request: httpx.Request) -> httpx.Response: +def sse_response(request: httpx2.Request) -> httpx2.Response: return httpx2.Response( 200, headers={"content-type": "text/event-stream"}, @@ -69,21 +56,21 @@ async def test_httpx2_helpers_supply_sdk_defaults_and_accept_native_proxy() -> N def test_sync_helper_preserves_httpx2_family_for_parsed_raw_and_sse() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] hooks: list[str] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) return sse_response(request) if request.url.path.endswith("/responses") else model_list(request) - def on_request(request: httpx.Request) -> None: + def on_request(request: httpx2.Request) -> None: hooks.append(type(request).__module__) with OpenAI( api_key="test", base_url=httpx2.URL("https://example.test/v1"), http_client=openai.DefaultHttpx2Client( - timeout=httpx.Timeout(30.0, read=10.0), + timeout=httpx2.Timeout(30.0, read=10.0), auth=httpx2.BasicAuth("fake-test-user", "fake-test-password"), headers=[("x-repeated", "one"), ("x-repeated", "two")], mounts={"https://example.test": httpx2.MockTransport(handler)}, @@ -100,7 +87,7 @@ def on_request(request: httpx.Request) -> None: "/multipart", files={"file": ("example.txt", b"body", "text/plain")}, options={"headers": {"Content-Type": "multipart/form-data"}}, - cast_to=httpx.Response, + cast_to=httpx2.Response, ) assert parsed.object == "list" @@ -122,21 +109,21 @@ def on_request(request: httpx.Request) -> None: async def test_async_helper_preserves_httpx2_family_for_parsed_raw_and_sse() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] hooks: list[str] = [] - async def handler(request: httpx.Request) -> httpx.Response: + async def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) return sse_response(request) if request.url.path.endswith("/responses") else model_list(request) - async def on_request(request: httpx.Request) -> None: + async def on_request(request: httpx2.Request) -> None: hooks.append(type(request).__module__) async with AsyncOpenAI( api_key="test", base_url=httpx2.URL("https://example.test/v1"), http_client=openai.DefaultAsyncHttpx2Client( - timeout=httpx.Timeout(30.0, read=10.0), + timeout=httpx2.Timeout(30.0, read=10.0), auth=httpx2.BasicAuth("fake-test-user", "fake-test-password"), headers=[("x-repeated", "one"), ("x-repeated", "two")], transport=httpx2.MockTransport(handler), @@ -153,7 +140,7 @@ async def on_request(request: httpx.Request) -> None: "/multipart", files={"file": ("example.txt", b"body", "text/plain")}, options={"headers": {"Content-Type": "multipart/form-data"}}, - cast_to=httpx.Response, + cast_to=httpx2.Response, ) assert parsed.object == "list" @@ -196,7 +183,7 @@ async def test_httpx2_urls_and_response_casts() -> None: class ResponseSubclass(httpx2.Response): pass - def model(request: httpx.Request) -> httpx.Response: + def model(request: httpx2.Request) -> httpx2.Response: return httpx2.Response( 200, request=request, @@ -218,7 +205,7 @@ def model(request: httpx.Request) -> httpx.Response: with pytest.raises(ValueError, match="Subclasses of HTTP response classes"): client.get("/models", cast_to=ResponseSubclass) - async def handler(request: httpx.Request) -> httpx.Response: + async def handler(request: httpx2.Request) -> httpx2.Response: return model(request) async with AsyncOpenAI( @@ -308,14 +295,14 @@ async def test_httpx2_urls_work_for_all_websocket_builders() -> None: async def test_httpx2_native_timeouts_set_numeric_read_timeout_header() -> None: - sync_requests: list[httpx.Request] = [] - async_requests: list[httpx.Request] = [] + sync_requests: list[httpx2.Request] = [] + async_requests: list[httpx2.Request] = [] - def sync_handler(request: httpx.Request) -> httpx.Response: + def sync_handler(request: httpx2.Request) -> httpx2.Response: sync_requests.append(request) return model_list(request) - async def async_handler(request: httpx.Request) -> httpx.Response: + async def async_handler(request: httpx2.Request) -> httpx2.Response: async_requests.append(request) return model_list(request) @@ -344,7 +331,7 @@ async def async_handler(request: httpx.Request) -> httpx.Response: async def test_direct_async_injection() -> None: - async def handler(request: httpx.Request) -> httpx.Response: + async def handler(request: httpx2.Request) -> httpx2.Response: return model_list(request) direct = httpx2.AsyncClient(transport=httpx2.MockTransport(handler), trust_env=False) @@ -357,9 +344,9 @@ async def handler(request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failure", ["timeout", "connection", "status"]) def test_sync_retries_and_failure_families(failure: str, monkeypatch: pytest.MonkeyPatch) -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) if len(requests) > 1: return model_list(request) @@ -388,7 +375,7 @@ def no_sleep(**_kwargs: Any) -> None: assert len(requests) == 2 assert all(type(request).__module__ == "httpx2" for request in requests) - def always_fail(request: httpx.Request) -> httpx.Response: + def always_fail(request: httpx2.Request) -> httpx2.Response: if failure == "timeout": raise httpx2.ReadTimeout("timeout", request=request) if failure == "connection": @@ -419,9 +406,9 @@ def always_fail(request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failure", ["timeout", "connection", "status"]) async def test_async_retries_and_failure_families(failure: str, monkeypatch: pytest.MonkeyPatch) -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - async def handler(request: httpx.Request) -> httpx.Response: + async def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) if len(requests) > 1: return model_list(request) @@ -450,7 +437,7 @@ async def no_sleep(**_kwargs: Any) -> None: assert len(requests) == 2 assert all(type(request).__module__ == "httpx2" for request in requests) - async def always_fail(request: httpx.Request) -> httpx.Response: + async def always_fail(request: httpx2.Request) -> httpx2.Response: if failure == "timeout": raise httpx2.ReadTimeout("timeout", request=request) if failure == "connection": @@ -480,14 +467,14 @@ async def always_fail(request: httpx.Request) -> httpx.Response: async def test_provider_auth_and_stream_consumed_families() -> None: - sync_requests: list[httpx.Request] = [] - async_requests: list[httpx.Request] = [] + sync_requests: list[httpx2.Request] = [] + async_requests: list[httpx2.Request] = [] - def sync_handler(request: httpx.Request) -> httpx.Response: + def sync_handler(request: httpx2.Request) -> httpx2.Response: sync_requests.append(request) return model_list(request) - async def async_handler(request: httpx.Request) -> httpx.Response: + async def async_handler(request: httpx2.Request) -> httpx2.Response: async_requests.append(request) return model_list(request) @@ -509,35 +496,39 @@ async def async_handler(request: httpx.Request) -> httpx.Response: assert async_requests[0].headers["authorization"] == "Bearer bedrock-token" class SyncStream(httpx2.SyncByteStream): + @override def __iter__(self): yield b'{"object":"list","data":[]}' class AsyncStream(httpx2.AsyncByteStream): + @override async def __aiter__(self): yield b'{"object":"list","data":[]}' class FailingSyncStream(httpx2.SyncByteStream): + @override def __iter__(self): yield b"partial" raise httpx2.ReadTimeout("stream timeout") class FailingAsyncStream(httpx2.AsyncByteStream): + @override async def __aiter__(self): yield b"partial" raise httpx2.ReadTimeout("stream timeout") - def sync_stream_handler(request: httpx.Request) -> httpx.Response: + def sync_stream_handler(request: httpx2.Request) -> httpx2.Response: return httpx2.Response(200, headers={"content-type": "application/json"}, stream=SyncStream(), request=request) - async def async_stream_handler(request: httpx.Request) -> httpx.Response: + async def async_stream_handler(request: httpx2.Request) -> httpx2.Response: return httpx2.Response(200, headers={"content-type": "application/json"}, stream=AsyncStream(), request=request) - def sync_failing_stream_handler(request: httpx.Request) -> httpx.Response: + def sync_failing_stream_handler(request: httpx2.Request) -> httpx2.Response: return httpx2.Response( 200, headers={"content-type": "application/json"}, stream=FailingSyncStream(), request=request ) - async def async_failing_stream_handler(request: httpx.Request) -> httpx.Response: + async def async_failing_stream_handler(request: httpx2.Request) -> httpx2.Response: return httpx2.Response( 200, headers={"content-type": "application/json"}, stream=FailingAsyncStream(), request=request ) @@ -616,21 +607,23 @@ async def on_exception(self, exception: Exception) -> None: self.exception = exception class FailingSyncStream(httpx2.SyncByteStream): + @override def __iter__(self): yield b"partial" raise httpx2.ReadTimeout("assistant stream timeout") class FailingAsyncStream(httpx2.AsyncByteStream): + @override async def __aiter__(self): yield b"partial" raise httpx2.ReadTimeout("assistant stream timeout") - def sync_response(request: httpx.Request) -> httpx.Response: + def sync_response(request: httpx2.Request) -> httpx2.Response: return httpx2.Response( 200, headers={"content-type": "text/event-stream"}, stream=FailingSyncStream(), request=request ) - async def async_response(request: httpx.Request) -> httpx.Response: + async def async_response(request: httpx2.Request) -> httpx2.Response: return httpx2.Response( 200, headers={"content-type": "text/event-stream"}, stream=FailingAsyncStream(), request=request ) @@ -671,14 +664,14 @@ async def async_response(request: httpx.Request) -> httpx.Response: async def test_sigv4_provider_preserves_httpx2_family_and_rejects_one_shot_bodies() -> None: pytest.importorskip("botocore") - sync_requests: list[httpx.Request] = [] - async_requests: list[httpx.Request] = [] + sync_requests: list[httpx2.Request] = [] + async_requests: list[httpx2.Request] = [] - def sync_handler(request: httpx.Request) -> httpx.Response: + def sync_handler(request: httpx2.Request) -> httpx2.Response: sync_requests.append(request) return model_list(request) - async def async_handler(request: httpx.Request) -> httpx.Response: + async def async_handler(request: httpx2.Request) -> httpx2.Response: async_requests.append(request) return model_list(request) @@ -695,9 +688,9 @@ async def async_handler(request: httpx.Request) -> httpx.Response: http_client=openai.DefaultHttpx2Client(transport=httpx2.MockTransport(sync_handler), trust_env=False), max_retries=0, ) as sync_client: - sync_client.post("/responses", content=b"body", cast_to=httpx.Response) + sync_client.post("/responses", content=b"body", cast_to=httpx2.Response) with pytest.raises(OpenAIError, match="requires a replayable request body"): - sync_client.post("/responses", content=iter([b"body"]), cast_to=httpx.Response) + sync_client.post("/responses", content=iter([b"body"]), cast_to=httpx2.Response) async def body(): yield b"body" @@ -707,9 +700,9 @@ async def body(): http_client=openai.DefaultAsyncHttpx2Client(transport=httpx2.MockTransport(async_handler), trust_env=False), max_retries=0, ) as async_client: - await async_client.post("/responses", content=b"body", cast_to=httpx.Response) + await async_client.post("/responses", content=b"body", cast_to=httpx2.Response) with pytest.raises(OpenAIError, match="requires a replayable request body"): - await async_client.post("/responses", content=body(), cast_to=httpx.Response) + await async_client.post("/responses", content=body(), cast_to=httpx2.Response) assert len(sync_requests) == 1 assert len(async_requests) == 1 diff --git a/tests/test_httpx2_base.py b/tests/test_httpx2_base.py index eb81c69a98..0d0b28351e 100644 --- a/tests/test_httpx2_base.py +++ b/tests/test_httpx2_base.py @@ -9,42 +9,28 @@ from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler from typing_extensions import override -import httpx -import respx +import httpx2 import pytest import openai -from openai import OpenAI, AsyncOpenAI, _httpx2 as httpx2_helpers +from openai import OpenAI, AsyncOpenAI +from tests.respx2 import MockRouter -def test_base_import_does_not_load_httpx2() -> None: - subprocess.run([sys.executable, "-c", "import sys; import openai; assert 'httpx2' not in sys.modules"], check=True) +def test_base_import_does_not_load_legacy_httpx() -> None: + subprocess.run( + [ + sys.executable, + "-c", + "import sys; import openai; assert 'httpx2' in sys.modules; assert 'httpx' not in sys.modules", + ], + check=True, + ) -def test_missing_httpx2_extra_is_actionable(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(httpx2_helpers.sys, "version_info", (3, 10)) - - def missing_httpx2(name: str) -> object: - raise ImportError(name) - - monkeypatch.setattr(httpx2_helpers.importlib, "import_module", missing_httpx2) - - for helper in (openai.DefaultHttpx2Client, openai.DefaultAsyncHttpx2Client): - with pytest.raises(RuntimeError, match=r"install the httpx2 extra: pip install 'openai\[httpx2\]'"): - helper() - - -def test_python39_httpx2_error_is_actionable(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(httpx2_helpers.sys, "version_info", (3, 9)) - - for helper in (openai.DefaultHttpx2Client, openai.DefaultAsyncHttpx2Client): - with pytest.raises(RuntimeError, match=r"HTTPX2 requires Python 3\.10 or later.*openai\[httpx2\]"): - helper() - - -@pytest.mark.respx(base_url="https://example.test/v1") -def test_default_httpx_family_and_respx_are_unchanged(respx_mock: respx.MockRouter) -> None: - route = respx_mock.get("/models").mock(return_value=httpx.Response(200, json={"object": "list", "data": []})) +@pytest.mark.respx2(base_url="https://example.test/v1") +def test_default_client_and_respx_use_httpx2(respx2_mock: MockRouter) -> None: + route = respx2_mock.get("/models").mock(return_value=httpx2.Response(200, json={"object": "list", "data": []})) with warnings.catch_warnings(record=True) as captured: warnings.simplefilter("always") @@ -53,35 +39,33 @@ def test_default_httpx_family_and_respx_are_unchanged(respx_mock: respx.MockRout assert route.called assert captured == [] - assert isinstance(response.http_response, httpx.Response) - assert isinstance(response.http_request, httpx.Request) + assert isinstance(response.http_response, httpx2.Response) + assert isinstance(response.http_request, httpx2.Request) -async def test_existing_httpx_helpers_and_injected_clients_are_unchanged() -> None: - class SyncHttpxClient(openai.DefaultHttpxClient): +async def test_existing_http_client_helpers_default_to_httpx2() -> None: + class SyncHttpClient(openai.DefaultHttpxClient): pass - class AsyncHttpxClient(openai.DefaultAsyncHttpxClient): + class AsyncHttpClient(openai.DefaultAsyncHttpxClient): pass - def handler(request: httpx.Request) -> httpx.Response: - return httpx.Response(200, json={"object": "list", "data": []}, request=request) + def handler(request: httpx2.Request) -> httpx2.Response: + return httpx2.Response(200, json={"object": "list", "data": []}, request=request) - with SyncHttpxClient(transport=httpx.MockTransport(handler), trust_env=False) as http_client: + with SyncHttpClient(transport=httpx2.MockTransport(handler), trust_env=False) as http_client: with OpenAI(api_key="test", base_url="https://example.test/v1", http_client=http_client) as client: response = client.models.with_raw_response.list() - assert isinstance(response.http_response, httpx.Response) + assert isinstance(response.http_response, httpx2.Response) - async with AsyncHttpxClient(transport=httpx.MockTransport(handler), trust_env=False) as http_client: + async with AsyncHttpClient(transport=httpx2.MockTransport(handler), trust_env=False) as http_client: async with AsyncOpenAI(api_key="test", base_url="https://example.test/v1", http_client=http_client) as client: response = await client.models.with_raw_response.list() - assert isinstance(response.http_response, httpx.Response) - + assert isinstance(response.http_response, httpx2.Response) -async def test_existing_aiohttp_adapter_is_unchanged_when_installed() -> None: - if importlib.util.find_spec("httpx_aiohttp") is None: - pytest.skip("the aiohttp extra is not installed") +@pytest.mark.skipif(importlib.util.find_spec("aiohttp") is None, reason="the aiohttp extra is not installed") +async def test_default_aiohttp_client_uses_httpx2() -> None: class ModelsHandler(BaseHTTPRequestHandler): def do_GET(self) -> None: self.send_response(200) @@ -103,10 +87,11 @@ def log_message(self, format: str, *_args: object) -> None: # noqa: A002 http_client=openai.DefaultAioHttpClient(), max_retries=0, ) as client: + assert isinstance(client._client, httpx2.AsyncClient) response = await client.models.with_raw_response.list() finally: await asyncio.to_thread(server.shutdown) thread.join() server.server_close() - assert isinstance(response.http_response, httpx.Response) + assert isinstance(response.http_response, httpx2.Response) diff --git a/tests/test_httpx2_respx.py b/tests/test_httpx2_respx.py index 37c42d9628..ab73b196bc 100644 --- a/tests/test_httpx2_respx.py +++ b/tests/test_httpx2_respx.py @@ -2,27 +2,23 @@ import os -import httpx +import httpx2 import pytest -from respx import MockRouter from openai import OpenAI, AsyncOpenAI, APITimeoutError +from tests.respx2 import MockRouter -httpx2 = pytest.importorskip("httpx2") base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -pytestmark = pytest.mark.skipif( - os.environ.get("OPENAI_TEST_HTTP_CLIENT") != "httpx2", reason="requires the HTTPX2 test lane" -) -@pytest.mark.respx(base_url=base_url) -def test_respx_bridge_preserves_native_sync_family_and_request_content(client: OpenAI, respx_mock: MockRouter) -> None: - def mirror(request: httpx.Request) -> httpx.Response: +@pytest.mark.respx2(base_url=base_url) +def test_respx2_preserves_native_sync_family_and_request_content(client: OpenAI, respx2_mock: MockRouter) -> None: + def mirror(request: httpx2.Request) -> httpx2.Response: assert request.url.path == "/upload" assert request.headers["x-test"] == "sync" - return httpx.Response(200, content=request.content) + return httpx2.Response(200, content=request.content) - respx_mock.post("/upload").mock(side_effect=mirror) + respx2_mock.post("/upload").mock(side_effect=mirror) response = client.post( "/upload", content=b"sync body", options={"headers": {"x-test": "sync"}}, cast_to=httpx2.Response @@ -31,19 +27,19 @@ def mirror(request: httpx.Request) -> httpx.Response: assert isinstance(response, httpx2.Response) assert isinstance(response.request, httpx2.Request) assert response.content == b"sync body" - assert len(respx_mock.calls) == 1 + assert len(respx2_mock.calls) == 1 -@pytest.mark.respx(base_url=base_url) -async def test_respx_bridge_preserves_native_async_family_and_request_content( - async_client: AsyncOpenAI, respx_mock: MockRouter +@pytest.mark.respx2(base_url=base_url) +async def test_respx2_preserves_native_async_family_and_request_content( + async_client: AsyncOpenAI, respx2_mock: MockRouter ) -> None: - async def mirror(request: httpx.Request) -> httpx.Response: + async def mirror(request: httpx2.Request) -> httpx2.Response: assert request.url.path == "/upload" assert request.headers["x-test"] == "async" - return httpx.Response(200, content=await request.aread()) + return httpx2.Response(200, content=await request.aread()) - respx_mock.post("/upload").mock(side_effect=mirror) + respx2_mock.post("/upload").mock(side_effect=mirror) response = await async_client.post( "/upload", content=b"async body", options={"headers": {"x-test": "async"}}, cast_to=httpx2.Response @@ -52,12 +48,12 @@ async def mirror(request: httpx.Request) -> httpx.Response: assert isinstance(response, httpx2.Response) assert isinstance(response.request, httpx2.Request) assert response.content == b"async body" - assert len(respx_mock.calls) == 1 + assert len(respx2_mock.calls) == 1 -@pytest.mark.respx(base_url=base_url) -def test_respx_bridge_maps_timeout_to_native_family(client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/models").mock(side_effect=httpx.ReadTimeout("mock timeout")) +@pytest.mark.respx2(base_url=base_url) +def test_respx2_maps_timeout_to_native_family(client: OpenAI, respx2_mock: MockRouter) -> None: + respx2_mock.get("/models").mock(side_effect=httpx2.ReadTimeout("mock timeout")) with pytest.raises(APITimeoutError) as exc_info: client.with_options(max_retries=0).models.list() diff --git a/tests/test_httpx2_workload.py b/tests/test_httpx2_workload.py index 14bb997248..fdc787c18c 100644 --- a/tests/test_httpx2_workload.py +++ b/tests/test_httpx2_workload.py @@ -1,12 +1,10 @@ from __future__ import annotations import json -from typing import Any, NoReturn, cast +from typing import Any -import httpx -import respx +import httpx2 import pytest -from respx.models import Call import openai._base_client as base_client import openai.auth._workload as workload @@ -21,8 +19,6 @@ ) from openai.auth import WorkloadIdentity -httpx2 = pytest.importorskip("httpx2") - def workload_identity(get_token: Any = lambda: "subject-token") -> WorkloadIdentity: return { @@ -36,10 +32,6 @@ def exchange_payload(access_token: str) -> dict[str, object]: return {"access_token": access_token, "expires_in": 3600} -def forbidden_httpx_send(*_args: Any, **_kwargs: Any) -> NoReturn: - pytest.fail("HTTPX unexpectedly sent a request") - - def test_sync_httpx2_workload_exchange_is_native_and_cached(monkeypatch: pytest.MonkeyPatch) -> None: exchange_requests: list[Any] = [] api_requests: list[Any] = [] @@ -63,7 +55,6 @@ def api_handler(request: Any) -> Any: return httpx2.Response(200, request=request, json={"object": "list", "data": []}) monkeypatch.setattr(workload, "DefaultHttpx2Client", exchange_client) - monkeypatch.setattr(httpx.Client, "send", forbidden_httpx_send) with OpenAI( workload_identity=workload_identity(get_token), @@ -111,8 +102,6 @@ async def api_handler(request: Any) -> Any: return httpx2.Response(status_code, request=request, json={"object": "list", "data": []}) monkeypatch.setattr(workload, "DefaultHttpx2Client", exchange_client) - monkeypatch.setattr(httpx.Client, "send", forbidden_httpx_send) - monkeypatch.setattr(httpx.AsyncClient, "send", forbidden_httpx_send) async with AsyncOpenAI( workload_identity=workload_identity(), @@ -150,7 +139,6 @@ def default_client(**kwargs: Any) -> Any: monkeypatch.setattr(base_client, "SyncHttpxClientWrapper", default_client) monkeypatch.setattr(workload, "DefaultHttpx2Client", exchange_client) - monkeypatch.setattr(httpx.Client, "send", forbidden_httpx_send) with OpenAI( workload_identity=workload_identity(), @@ -188,8 +176,6 @@ def default_client(**kwargs: Any) -> Any: monkeypatch.setattr(base_client, "AsyncHttpxClientWrapper", default_client) monkeypatch.setattr(workload, "DefaultHttpx2Client", exchange_client) - monkeypatch.setattr(httpx.Client, "send", forbidden_httpx_send) - monkeypatch.setattr(httpx.AsyncClient, "send", forbidden_httpx_send) async with AsyncOpenAI( workload_identity=workload_identity(), @@ -273,33 +259,3 @@ def api_handler(request: Any) -> Any: client.models.list() assert type(exc_info.value.__cause__).__module__ == "httpx2" - - -def test_httpx_workload_exchange_stays_httpx_when_httpx2_is_installed(monkeypatch: pytest.MonkeyPatch) -> None: - api_requests: list[httpx.Request] = [] - - def forbidden_httpx2(**_kwargs: Any) -> Any: - pytest.fail("HTTPX2 unexpectedly created a workload exchange client") - - def api_handler(request: httpx.Request) -> httpx.Response: - api_requests.append(request) - return httpx.Response(200, request=request, json={"object": "list", "data": []}) - - monkeypatch.setattr(workload, "DefaultHttpx2Client", forbidden_httpx2) - - with respx.mock(assert_all_mocked=False) as router: - exchange = router.post("https://auth.openai.com/oauth/token").mock( - return_value=httpx.Response(200, json=exchange_payload("access-token")) - ) - with OpenAI( - workload_identity=workload_identity(), - base_url="https://api.example.test/v1", - http_client=httpx.Client(transport=httpx.MockTransport(api_handler), trust_env=False), - max_retries=0, - ) as client: - assert client.models.list().object == "list" - - assert exchange.call_count == 1 - assert len(api_requests) == 1 - assert isinstance(cast(Call, exchange.calls[0]).request, httpx.Request) - assert isinstance(api_requests[0], httpx.Request) diff --git a/tests/test_httpx_compat.py b/tests/test_httpx_compat.py new file mode 100644 index 0000000000..cb00e9b00f --- /dev/null +++ b/tests/test_httpx_compat.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +import os +import asyncio +import importlib +import threading +from typing import Any, cast +from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler +from typing_extensions import override + +import pytest + +from openai import OpenAI, AsyncOpenAI + +pytestmark = pytest.mark.skipif( + os.environ.get("OPENAI_TEST_LEGACY_HTTPX") != "1", reason="requires the dedicated legacy HTTPX compatibility lane" +) + + +def test_external_legacy_httpx_client_is_supported() -> None: + httpx = cast(Any, importlib.import_module("httpx")) + + def handler(request: Any) -> Any: + return httpx.Response(200, request=request, json={"object": "list", "data": []}) + + with OpenAI( + api_key="test", + base_url="https://example.test/v1", + http_client=httpx.Client(transport=httpx.MockTransport(handler), trust_env=False), + max_retries=0, + ) as client: + assert isinstance(client._client, httpx.Client) + response = client.models.list() + + assert response.data is not None + + +async def test_external_legacy_async_httpx_client_is_supported() -> None: + httpx = cast(Any, importlib.import_module("httpx")) + + async def handler(request: Any) -> Any: + return httpx.Response(200, request=request, json={"object": "list", "data": []}) + + async with AsyncOpenAI( + api_key="test", + base_url="https://example.test/v1", + http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler), trust_env=False), + max_retries=0, + ) as client: + assert isinstance(client._client, httpx.AsyncClient) + response = await client.models.list() + + assert response.data is not None + + +async def test_external_legacy_aiohttp_client_is_supported() -> None: + httpx = cast(Any, importlib.import_module("httpx")) + HttpxAiohttpClient = cast(Any, importlib.import_module("httpx_aiohttp")).HttpxAiohttpClient + + class ModelsHandler(BaseHTTPRequestHandler): + def do_GET(self) -> None: + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(b'{"object":"list","data":[]}') + + @override + def log_message(self, format: str, *_args: object) -> None: # noqa: A002 + return None + + server = ThreadingHTTPServer(("127.0.0.1", 0), ModelsHandler) + thread = threading.Thread(target=server.serve_forever) + thread.start() + try: + async with AsyncOpenAI( + api_key="test", + base_url=f"http://127.0.0.1:{server.server_port}/v1", + http_client=HttpxAiohttpClient(), + max_retries=0, + ) as client: + assert isinstance(client._client, httpx.AsyncClient) + response = await client.models.list() + finally: + await asyncio.to_thread(server.shutdown) + thread.join() + server.server_close() + + assert response.data is not None diff --git a/tests/test_legacy_response.py b/tests/test_legacy_response.py index 9da1a80659..236c6c259b 100644 --- a/tests/test_legacy_response.py +++ b/tests/test_legacy_response.py @@ -2,7 +2,7 @@ from typing import Any, Union, cast from typing_extensions import Annotated -import httpx +import httpx2 import pytest import pydantic @@ -19,7 +19,7 @@ class PydanticModel(pydantic.BaseModel): ... def test_response_parse_mismatched_basemodel(client: OpenAI) -> None: response = LegacyAPIResponse( - raw=httpx.Response(200, content=b"foo"), + raw=httpx2.Response(200, content=b"foo"), client=client, stream=False, stream_cls=None, @@ -47,7 +47,7 @@ def test_response_parse_mismatched_basemodel(client: OpenAI) -> None: ) def test_response_parse_bool(client: OpenAI, content: str, expected: bool) -> None: response = LegacyAPIResponse( - raw=httpx.Response(200, content=content), + raw=httpx2.Response(200, content=content), client=client, stream=False, stream_cls=None, @@ -61,7 +61,7 @@ def test_response_parse_bool(client: OpenAI, content: str, expected: bool) -> No def test_response_parse_custom_stream(client: OpenAI) -> None: response = LegacyAPIResponse( - raw=httpx.Response(200, content=b"foo"), + raw=httpx2.Response(200, content=b"foo"), client=client, stream=True, stream_cls=None, @@ -80,7 +80,7 @@ class CustomModel(BaseModel): def test_response_parse_custom_model(client: OpenAI) -> None: response = LegacyAPIResponse( - raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + raw=httpx2.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=client, stream=False, stream_cls=None, @@ -95,7 +95,7 @@ def test_response_parse_custom_model(client: OpenAI) -> None: def test_response_basemodel_request_id(client: OpenAI) -> None: response = LegacyAPIResponse( - raw=httpx.Response( + raw=httpx2.Response( 200, headers={"x-request-id": "my-req-id"}, content=json.dumps({"foo": "hello!", "bar": 2}), @@ -118,7 +118,7 @@ def test_response_basemodel_request_id(client: OpenAI) -> None: def test_response_parse_annotated_type(client: OpenAI) -> None: response = LegacyAPIResponse( - raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + raw=httpx2.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=client, stream=False, stream_cls=None, @@ -140,7 +140,7 @@ class OtherModel(pydantic.BaseModel): @pytest.mark.parametrize("client", [False], indirect=True) # loose validation def test_response_parse_expect_model_union_non_json_content(client: OpenAI) -> None: response = LegacyAPIResponse( - raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), + raw=httpx2.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), client=client, stream=False, stream_cls=None, diff --git a/tests/test_module_client.py b/tests/test_module_client.py index cb509d3d19..ff099d5056 100644 --- a/tests/test_module_client.py +++ b/tests/test_module_client.py @@ -4,9 +4,9 @@ import os as _os -import httpx +import httpx2 import pytest -from httpx import URL +from httpx2 import URL import openai from openai import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES @@ -94,7 +94,7 @@ def test_http_client_option() -> None: original_http_client = openai.completions._client._client assert original_http_client is not None - new_client = httpx.Client() + new_client = httpx2.Client() openai.http_client = new_client assert openai.completions._client._client is new_client @@ -254,15 +254,15 @@ def test_bedrock_module_api_key_overrides_cached_env_token_after_load() -> None: def test_bedrock_module_api_key_switches_cached_aws_client_to_bearer() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) with fresh_env(): openai.api_type = "amazon-bedrock" - openai.http_client = httpx.Client(transport=httpx.MockTransport(handler), trust_env=False) + openai.http_client = httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False) _os.environ["AWS_ACCESS_KEY_ID"] = "access key" _os.environ["AWS_SECRET_ACCESS_KEY"] = "secret key" _os.environ["AWS_REGION"] = "us-west-2" @@ -272,7 +272,7 @@ def handler(request: httpx.Request) -> httpx.Response: assert client._uses_aws_auth() openai.api_key = "new Bedrock token" - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) assert requests[0].headers["Authorization"] == "Bearer new Bedrock token" @@ -290,7 +290,7 @@ def test_bedrock_api_type_uses_token_provider_without_mutating_module_api_key() def test_bedrock_module_api_key_overrides_cached_token_provider() -> None: - requests: list[httpx.Request] = [] + requests: list[httpx2.Request] = [] provider_calls = 0 def token_provider() -> str: @@ -298,21 +298,21 @@ def token_provider() -> str: provider_calls += 1 raise AssertionError("the replaced token provider must not be called") - def handler(request: httpx.Request) -> httpx.Response: + def handler(request: httpx2.Request) -> httpx2.Response: requests.append(request) - return httpx.Response(200, request=request, json={}) + return httpx2.Response(200, request=request, json={}) with fresh_env(): openai.api_type = "amazon-bedrock" openai.bedrock_token_provider = token_provider - openai.http_client = httpx.Client(transport=httpx.MockTransport(handler), trust_env=False) + openai.http_client = httpx2.Client(transport=httpx2.MockTransport(handler), trust_env=False) _os.environ["AWS_REGION"] = "us-west-2" client = openai.responses._client assert isinstance(client, BedrockOpenAI) openai.api_key = "new Bedrock token" - client.get("/models", cast_to=httpx.Response) + client.get("/models", cast_to=httpx2.Response) assert provider_calls == 0 assert requests[0].headers["Authorization"] == "Bearer new Bedrock token" diff --git a/tests/test_response.py b/tests/test_response.py index 43f24c150d..fba775e873 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -2,7 +2,7 @@ from typing import Any, List, Union, cast from typing_extensions import Annotated -import httpx +import httpx2 import pytest import pydantic @@ -27,7 +27,7 @@ class ConcreteBaseAPIResponse(APIResponse[bytes]): ... class ConcreteAPIResponse(APIResponse[List[str]]): ... -class ConcreteAsyncAPIResponse(APIResponse[httpx.Response]): ... +class ConcreteAsyncAPIResponse(APIResponse[httpx2.Response]): ... def test_extract_response_type_direct_classes() -> None: @@ -47,7 +47,7 @@ def test_extract_response_type_direct_class_missing_type_arg() -> None: def test_extract_response_type_concrete_subclasses() -> None: assert extract_response_type(ConcreteBaseAPIResponse) == bytes assert extract_response_type(ConcreteAPIResponse) == List[str] - assert extract_response_type(ConcreteAsyncAPIResponse) == httpx.Response + assert extract_response_type(ConcreteAsyncAPIResponse) == httpx2.Response def test_extract_response_type_binary_response() -> None: @@ -60,7 +60,7 @@ class PydanticModel(pydantic.BaseModel): ... def test_response_parse_mismatched_basemodel(client: OpenAI) -> None: response = APIResponse( - raw=httpx.Response(200, content=b"foo"), + raw=httpx2.Response(200, content=b"foo"), client=client, stream=False, stream_cls=None, @@ -78,7 +78,7 @@ def test_response_parse_mismatched_basemodel(client: OpenAI) -> None: @pytest.mark.asyncio async def test_async_response_parse_mismatched_basemodel(async_client: AsyncOpenAI) -> None: response = AsyncAPIResponse( - raw=httpx.Response(200, content=b"foo"), + raw=httpx2.Response(200, content=b"foo"), client=async_client, stream=False, stream_cls=None, @@ -95,7 +95,7 @@ async def test_async_response_parse_mismatched_basemodel(async_client: AsyncOpen def test_response_parse_custom_stream(client: OpenAI) -> None: response = APIResponse( - raw=httpx.Response(200, content=b"foo"), + raw=httpx2.Response(200, content=b"foo"), client=client, stream=True, stream_cls=None, @@ -110,7 +110,7 @@ def test_response_parse_custom_stream(client: OpenAI) -> None: @pytest.mark.asyncio async def test_async_response_parse_custom_stream(async_client: AsyncOpenAI) -> None: response = AsyncAPIResponse( - raw=httpx.Response(200, content=b"foo"), + raw=httpx2.Response(200, content=b"foo"), client=async_client, stream=True, stream_cls=None, @@ -129,7 +129,7 @@ class CustomModel(BaseModel): def test_response_parse_custom_model(client: OpenAI) -> None: response = APIResponse( - raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + raw=httpx2.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=client, stream=False, stream_cls=None, @@ -145,7 +145,7 @@ def test_response_parse_custom_model(client: OpenAI) -> None: @pytest.mark.asyncio async def test_async_response_parse_custom_model(async_client: AsyncOpenAI) -> None: response = AsyncAPIResponse( - raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + raw=httpx2.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=async_client, stream=False, stream_cls=None, @@ -160,7 +160,7 @@ async def test_async_response_parse_custom_model(async_client: AsyncOpenAI) -> N def test_response_basemodel_request_id(client: OpenAI) -> None: response = APIResponse( - raw=httpx.Response( + raw=httpx2.Response( 200, headers={"x-request-id": "my-req-id"}, content=json.dumps({"foo": "hello!", "bar": 2}), @@ -184,7 +184,7 @@ def test_response_basemodel_request_id(client: OpenAI) -> None: @pytest.mark.asyncio async def test_async_response_basemodel_request_id(client: OpenAI) -> None: response = AsyncAPIResponse( - raw=httpx.Response( + raw=httpx2.Response( 200, headers={"x-request-id": "my-req-id"}, content=json.dumps({"foo": "hello!", "bar": 2}), @@ -205,7 +205,7 @@ async def test_async_response_basemodel_request_id(client: OpenAI) -> None: def test_response_parse_annotated_type(client: OpenAI) -> None: response = APIResponse( - raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + raw=httpx2.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=client, stream=False, stream_cls=None, @@ -222,7 +222,7 @@ def test_response_parse_annotated_type(client: OpenAI) -> None: async def test_async_response_parse_annotated_type(async_client: AsyncOpenAI) -> None: response = AsyncAPIResponse( - raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + raw=httpx2.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=async_client, stream=False, stream_cls=None, @@ -250,7 +250,7 @@ async def test_async_response_parse_annotated_type(async_client: AsyncOpenAI) -> ) def test_response_parse_bool(client: OpenAI, content: str, expected: bool) -> None: response = APIResponse( - raw=httpx.Response(200, content=content), + raw=httpx2.Response(200, content=content), client=client, stream=False, stream_cls=None, @@ -275,7 +275,7 @@ def test_response_parse_bool(client: OpenAI, content: str, expected: bool) -> No ) async def test_async_response_parse_bool(client: AsyncOpenAI, content: str, expected: bool) -> None: response = AsyncAPIResponse( - raw=httpx.Response(200, content=content), + raw=httpx2.Response(200, content=content), client=client, stream=False, stream_cls=None, @@ -294,7 +294,7 @@ class OtherModel(BaseModel): @pytest.mark.parametrize("client", [False], indirect=True) # loose validation def test_response_parse_expect_model_union_non_json_content(client: OpenAI) -> None: response = APIResponse( - raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), + raw=httpx2.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), client=client, stream=False, stream_cls=None, @@ -311,7 +311,7 @@ def test_response_parse_expect_model_union_non_json_content(client: OpenAI) -> N @pytest.mark.parametrize("async_client", [False], indirect=True) # loose validation async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncOpenAI) -> None: response = AsyncAPIResponse( - raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), + raw=httpx2.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), client=async_client, stream=False, stream_cls=None, diff --git a/tests/test_streaming.py b/tests/test_streaming.py index 04f8e51abd..ae6c0590f7 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -2,7 +2,7 @@ from typing import Iterator, AsyncIterator -import httpx +import httpx2 import pytest from openai import OpenAI, AsyncOpenAI @@ -241,8 +241,8 @@ def make_event_iterator( async_client: AsyncOpenAI, ) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]: if sync: - return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events() + return Stream(cast_to=object, client=client, response=httpx2.Response(200, content=content))._iter_events() return AsyncStream( - cast_to=object, client=async_client, response=httpx.Response(200, content=to_aiter(content)) + cast_to=object, client=async_client, response=httpx2.Response(200, content=to_aiter(content)) )._iter_events() diff --git a/tests/test_utils/test_logging.py b/tests/test_utils/test_logging.py index cc018012e2..b7c53f5cae 100644 --- a/tests/test_utils/test_logging.py +++ b/tests/test_utils/test_logging.py @@ -4,6 +4,7 @@ import pytest from openai._utils import SensitiveHeadersFilter +from openai._utils._logs import setup_logging @pytest.fixture @@ -98,3 +99,17 @@ def test_standard_debug_msg(logger_with_filter: logging.Logger, caplog: pytest.L with caplog.at_level(logging.DEBUG): logger_with_filter.debug("Sending HTTP Request: %s %s", "POST", "chat/completions") assert caplog.messages[0] == "Sending HTTP Request: POST chat/completions" + + +@pytest.mark.parametrize(("setting", "level"), [("debug", logging.DEBUG), ("info", logging.INFO)]) +def test_httpx2_logger_follows_sdk_log_level(setting: str, level: int, monkeypatch: pytest.MonkeyPatch) -> None: + sdk_logger = logging.getLogger("openai") + transport_logger = logging.getLogger("httpx2") + monkeypatch.setattr(sdk_logger, "level", sdk_logger.level) + monkeypatch.setattr(transport_logger, "level", transport_logger.level) + monkeypatch.setenv("OPENAI_LOG", setting) + + setup_logging() + + assert sdk_logger.level == level + assert transport_logger.level == level diff --git a/uv.lock b/uv.lock index 903d4c4093..ab8356f146 100644 --- a/uv.lock +++ b/uv.lock @@ -225,15 +225,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/f2/4bd8f2f419088feb3ce55f0ca91040ff902f402edfd197450b20a2e1d533/botocore-1.43.46-py3-none-any.whl", hash = "sha256:cb673891e623ae6e6a1bf24d94ef169504f3eb02584adb5d5bee2f6aae819b60", size = 15380350, upload-time = "2026-07-10T19:31:57.616Z" }, ] -[[package]] -name = "certifi" -version = "2026.6.17" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, -] - [[package]] name = "cffi" version = "2.0.0" @@ -476,19 +467,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - [[package]] name = "httpcore2" version = "2.7.0" @@ -502,34 +480,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/6c/62e2e279e63fc4f7a5ee841ef13175a8bbc613f258e9dcc186e9de803a42/httpcore2-2.7.0-py3-none-any.whl", hash = "sha256:1452f589fe23f55b44546cd884294c41a29330af902bc0b71a761fd52d18f92b", size = 81506, upload-time = "2026-07-14T20:39:58.053Z" }, ] -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[[package]] -name = "httpx-aiohttp" -version = "0.1.12" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "httpx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/2c/b894861cecf030fb45675ea24aa55b5722e97c602a163d872fca66c5a6d8/httpx_aiohttp-0.1.12.tar.gz", hash = "sha256:81feec51fd82c0ecfa0e9aaf1b1a6c2591260d5e2bcbeb7eb0277a78e610df2c", size = 275945, upload-time = "2025-12-12T10:12:15.283Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/8d/85c9701e9af72ca132a1783e2a54364a90c6da832304416a30fc11196ab2/httpx_aiohttp-0.1.12-py3-none-any.whl", hash = "sha256:5b0eac39a7f360fa7867a60bcb46bb1024eada9c01cbfecdb54dc1edb3fb7141", size = 6367, upload-time = "2025-12-12T10:12:14.018Z" }, -] - [[package]] name = "httpx2" version = "2.7.0" @@ -959,12 +909,12 @@ wheels = [ [[package]] name = "openai" -version = "2.54.0" # x-release-please-version +version = "3.0.0" # x-release-please-version source = { editable = "." } dependencies = [ { name = "anyio" }, { name = "distro" }, - { name = "httpx" }, + { name = "httpx2" }, { name = "jiter" }, { name = "pydantic" }, { name = "sniffio" }, @@ -975,7 +925,6 @@ dependencies = [ [package.optional-dependencies] aiohttp = [ { name = "aiohttp" }, - { name = "httpx-aiohttp" }, ] bedrock = [ { name = "botocore" }, @@ -988,11 +937,6 @@ datalib = [ { name = "pandas-stubs", version = "2.3.3.260113", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "pandas-stubs", version = "3.0.3.260530", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -httpx2 = [ - { name = "anyio" }, - { name = "httpx" }, - { name = "httpx2" }, -] realtime = [ { name = "websockets" }, ] @@ -1005,14 +949,10 @@ voice-helpers = [ [package.metadata] requires-dist = [ { name = "aiohttp", marker = "extra == 'aiohttp'", specifier = ">=3.14.1" }, - { name = "anyio", specifier = ">=3.5.0,<5" }, - { name = "anyio", marker = "extra == 'httpx2'", specifier = ">=4.10.0,<5" }, + { name = "anyio", specifier = ">=4.10.0,<5" }, { name = "botocore", marker = "extra == 'bedrock'", specifier = ">=1.40.0,<2" }, { name = "distro", specifier = ">=1.7.0,<2" }, - { name = "httpx", specifier = ">=0.23.0,<1" }, - { name = "httpx", marker = "extra == 'httpx2'", specifier = ">=0.25.1,<1" }, - { name = "httpx-aiohttp", marker = "extra == 'aiohttp'", specifier = ">=0.1.9" }, - { name = "httpx2", marker = "extra == 'httpx2'", specifier = ">=2.7.0,<3" }, + { name = "httpx2", specifier = ">=2.7.0,<3" }, { name = "jiter", specifier = ">=0.10.0,<1" }, { name = "numpy", marker = "extra == 'datalib'", specifier = ">=1" }, { name = "numpy", marker = "extra == 'voice-helpers'", specifier = ">=2.0.2" }, @@ -1025,7 +965,7 @@ requires-dist = [ { name = "typing-extensions", specifier = ">=4.14,<5" }, { name = "websockets", marker = "extra == 'realtime'", specifier = ">=13,<16" }, ] -provides-extras = ["aiohttp", "httpx2", "realtime", "datalib", "voice-helpers", "bedrock"] +provides-extras = ["aiohttp", "realtime", "datalib", "voice-helpers", "bedrock"] [[package]] name = "pandas"