diff --git a/.castiron.stats.yml b/.castiron.stats.yml index e9b9830a3b..274aa342a4 100644 --- a/.castiron.stats.yml +++ b/.castiron.stats.yml @@ -1,8 +1,8 @@ schema_version: 1 -generation_id: d9e3b632-5822-43f8-a0de-0cd04d09db33 -openapi_spec_hash: 687580b2f55d2dada661f0387a4b3e0d -openapi_transformed_spec_hash: 904296217a5077aa14766c6c58aa87d9 +generation_id: 4bda3ef4-13a5-473f-b0cd-6fa6b69a0db8 +openapi_spec_hash: 6fbcb590bdf88b5d464546b290ab35e7 +openapi_transformed_spec_hash: 4e4a94f25153ab253178d6c098cb4f0e config_hash: 84cdcc959ee7140cc9a65e9b642c097b -codegen_sha: 41ac0d43560df6c0983b529a6112e2c437ead2d1 -codegen_hash: 614193cc88f476d3dbfe7d149d8adf21489f235af4454fb1e93628fe43e1e351 -public_codegen_sha: 2722089d4e9342c45f1ddbda6e7b4b72511d961f +codegen_sha: c11ad8ad6a402c5cabfff79093c308035fe39644 +codegen_hash: dc4765fced94aa65178e5fbda91d2844a712ac8d8e8b7fd8993fde98dbb1cb04 +public_codegen_sha: d54ac7ae41705340870935d4d6484843cb5d9d29 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3362d3a312..ca66de7f50 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.19.1" + ".": "3.19.2" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c54587372e..c0b5b98ae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [3.19.2](https://github.com/openai/openai-python/compare/v3.19.1...v3.19.2) (2026-09-23) + + +### Bug Fixes + +* preserve single files for fallback extraction paths ([#3875](https://github.com/openai/openai-python/issues/3875)) ([bfd3680](https://github.com/openai/openai-python/commit/bfd3680c8d3b715bb3175b82cd0fa1c1fb53162f)) + + +### Chores + +* **api:** clarify approximate web search location defaults ([#3953](https://github.com/openai/openai-python/issues/3953)) ([a95c95e](https://github.com/openai/openai-python/commit/a95c95ed372476168e9893723ff694ead5db8586)) +* **api:** clarify Realtime modality array definitions ([#3954](https://github.com/openai/openai-python/issues/3954)) ([e79cf53](https://github.com/openai/openai-python/commit/e79cf53878b1d8580fac3a46e375eb98d9ca161d)) +* **api:** correct fine-tuning bounds and Realtime response reference ([#3949](https://github.com/openai/openai-python/issues/3949)) ([325a948](https://github.com/openai/openai-python/commit/325a948a545add0201e082e778ca11b90ddb9426)) + ## [3.19.1](https://github.com/openai/openai-python/compare/v3.19.0...v3.19.1) (2026-09-23) diff --git a/api_reference/openapi.transformed.yml b/api_reference/openapi.transformed.yml index fdf4896149..3c69cd5bb0 100644 --- a/api_reference/openapi.transformed.yml +++ b/api_reference/openapi.transformed.yml @@ -36954,6 +36954,7 @@ components: description: | Scaling factor for the learning rate. A smaller learning rate may be useful to avoid overfitting. + default: auto anyOf: - type: string enum: @@ -36961,8 +36962,7 @@ components: x-stainless-const: true title: Auto - type: number - minimum: 0 - exclusiveMinimum: true + exclusiveMinimum: 0 n_epochs: description: | The number of epochs to train the model for. An epoch refers to one full cycle @@ -41281,15 +41281,15 @@ components: beta: description: | The beta value for the DPO method. A higher beta value will increase the weight of the penalty between the policy and reference model. + default: auto anyOf: - type: string enum: - auto x-stainless-const: true - type: number - minimum: 0 maximum: 2 - exclusiveMinimum: true + exclusiveMinimum: 0 batch_size: description: | Number of examples in each batch. A larger batch size means that model parameters are updated less frequently, but with lower variance. @@ -41305,14 +41305,14 @@ components: learning_rate_multiplier: description: | Scaling factor for the learning rate. A smaller learning rate may be useful to avoid overfitting. + default: auto anyOf: - type: string enum: - auto x-stainless-const: true - type: number - minimum: 0 - exclusiveMinimum: true + exclusiveMinimum: 0 n_epochs: description: | The number of epochs to train the model for. An epoch refers to one full cycle through the training dataset. @@ -41369,14 +41369,14 @@ components: learning_rate_multiplier: description: | Scaling factor for the learning rate. A smaller learning rate may be useful to avoid overfitting. + default: auto anyOf: - type: string enum: - auto x-stainless-const: true - type: number - minimum: 0 - exclusiveMinimum: true + exclusiveMinimum: 0 n_epochs: description: | The number of epochs to train the model for. An epoch refers to one full cycle through the training dataset. @@ -41402,15 +41402,15 @@ components: compute_multiplier: description: | Multiplier on amount of compute used for exploring search space during training. + default: auto anyOf: - type: string enum: - auto x-stainless-const: true - type: number - minimum: 0.00001 maximum: 10 - exclusiveMinimum: true + exclusiveMinimum: 0.00001 eval_interval: description: | The number of training steps between evaluation runs. @@ -41469,14 +41469,14 @@ components: learning_rate_multiplier: description: | Scaling factor for the learning rate. A smaller learning rate may be useful to avoid overfitting. + default: auto anyOf: - type: string enum: - auto x-stainless-const: true - type: number - minimum: 0 - exclusiveMinimum: true + exclusiveMinimum: 0 n_epochs: description: | The number of epochs to train the model for. An epoch refers to one full cycle through the training dataset. @@ -41649,6 +41649,7 @@ components: description: | Scaling factor for the learning rate. A smaller learning rate may be useful to avoid overfitting. + default: auto anyOf: - type: string enum: @@ -41656,8 +41657,7 @@ components: x-stainless-const: true title: Auto - type: number - minimum: 0 - exclusiveMinimum: true + exclusiveMinimum: 0 n_epochs: description: | The number of epochs to train the model for. An epoch refers to one full cycle @@ -51336,6 +51336,9 @@ components: The session configuration for either a realtime or transcription session. discriminator: propertyName: type + mapping: + realtime: '#/components/schemas/RealtimeSessionCreateResponseGA' + transcription: '#/components/schemas/RealtimeTranscriptionSessionCreateResponseGA' anyOf: - $ref: '#/components/schemas/RealtimeSessionCreateResponseGA' - $ref: '#/components/schemas/RealtimeTranscriptionSessionCreateResponseGA' @@ -54153,6 +54156,7 @@ components: - realtime.session description: The object type. Always `realtime.session`. modalities: + type: array description: | The set of modalities the model can respond with. To disable audio, set this to ["text"]. @@ -54366,6 +54370,7 @@ components: - value - expires_at modalities: + type: array description: | The set of modalities the model can respond with. To disable audio, set this to ["text"]. @@ -65318,7 +65323,9 @@ components: - type: object title: Web search approximate location description: | - The approximate location of the user. + The approximate location of the user. If omitted or null, defaults to the + United States. To avoid this fallback, pass `{"type": "approximate"}` without + location fields. To localize results, provide the relevant location fields. properties: type: type: string @@ -68571,7 +68578,7 @@ components: user_location: anyOf: - $ref: '#/components/schemas/ApproximateLocation' - description: The user's location. + description: 'The approximate location of the user. If omitted or null, defaults to the United States. To avoid this fallback, pass `{"type": "approximate"}` without location fields. To localize results, provide the relevant location fields.' - type: 'null' search_context_size: $ref: '#/components/schemas/SearchContextSize' @@ -84506,7 +84513,7 @@ components: user_location: anyOf: - $ref: '#/components/schemas/BetaApproximateLocation' - description: The user's location. + description: 'The approximate location of the user. If omitted or null, defaults to the United States. To avoid this fallback, pass `{"type": "approximate"}` without location fields. To localize results, provide the relevant location fields.' - type: 'null' search_context_size: $ref: '#/components/schemas/BetaSearchContextSize' @@ -85479,7 +85486,9 @@ components: - type: object title: Web search approximate location description: | - The approximate location of the user. + The approximate location of the user. If omitted or null, defaults to the + United States. To avoid this fallback, pass `{"type": "approximate"}` without + location fields. To localize results, provide the relevant location fields. properties: type: type: string diff --git a/pyproject.toml b/pyproject.toml index 12fe29e078..fb70a50977 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "openai" -version = "3.19.1" +version = "3.19.2" description = "The official Python library for the openai API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/openai/_utils/_utils.py b/src/openai/_utils/_utils.py index 02046a352c..95641eb158 100644 --- a/src/openai/_utils/_utils.py +++ b/src/openai/_utils/_utils.py @@ -114,7 +114,7 @@ def _extract_items( try: # Remove the field if there are no more dict keys in the path, # only "" traversal markers or end. - if all(p == "" for p in path[index:]): + if all(p == "" for p in path[index:]) and (index == len(path) or is_list(obj[key])): item = obj.pop(key) else: item = obj[key] diff --git a/src/openai/_version.py b/src/openai/_version.py index 02512a1a2a..e7c432efa8 100644 --- a/src/openai/_version.py +++ b/src/openai/_version.py @@ -1,2 +1,2 @@ __title__ = "openai" -__version__ = "3.19.1" # x-release-please-version +__version__ = "3.19.2" # x-release-please-version diff --git a/src/openai/types/beta/beta_web_search_preview_tool.py b/src/openai/types/beta/beta_web_search_preview_tool.py index 968e480885..967d7b7a38 100644 --- a/src/openai/types/beta/beta_web_search_preview_tool.py +++ b/src/openai/types/beta/beta_web_search_preview_tool.py @@ -9,7 +9,10 @@ class UserLocation(BaseModel): - """The user's location.""" + """The approximate location of the user. + + If omitted or null, defaults to the United States. To avoid this fallback, pass `{"type": "approximate"}` without location fields. To localize results, provide the relevant location fields. + """ type: Literal["approximate"] """The type of location approximation. Always `approximate`.""" @@ -55,4 +58,9 @@ class BetaWebSearchPreviewTool(BaseModel): """ user_location: Optional[UserLocation] = None - """The user's location.""" + """The approximate location of the user. + + If omitted or null, defaults to the United States. To avoid this fallback, pass + `{"type": "approximate"}` without location fields. To localize results, provide + the relevant location fields. + """ diff --git a/src/openai/types/beta/beta_web_search_preview_tool_param.py b/src/openai/types/beta/beta_web_search_preview_tool_param.py index 40b245d566..c5fae46f6a 100644 --- a/src/openai/types/beta/beta_web_search_preview_tool_param.py +++ b/src/openai/types/beta/beta_web_search_preview_tool_param.py @@ -9,7 +9,10 @@ class UserLocation(TypedDict, total=False): - """The user's location.""" + """The approximate location of the user. + + If omitted or null, defaults to the United States. To avoid this fallback, pass `{"type": "approximate"}` without location fields. To localize results, provide the relevant location fields. + """ type: Required[Literal["approximate"]] """The type of location approximation. Always `approximate`.""" @@ -55,4 +58,9 @@ class BetaWebSearchPreviewToolParam(TypedDict, total=False): """ user_location: Optional[UserLocation] - """The user's location.""" + """The approximate location of the user. + + If omitted or null, defaults to the United States. To avoid this fallback, pass + `{"type": "approximate"}` without location fields. To localize results, provide + the relevant location fields. + """ diff --git a/src/openai/types/beta/beta_web_search_tool.py b/src/openai/types/beta/beta_web_search_tool.py index bdf61e6d90..6adcf29869 100644 --- a/src/openai/types/beta/beta_web_search_tool.py +++ b/src/openai/types/beta/beta_web_search_tool.py @@ -22,7 +22,12 @@ class Filters(BaseModel): class UserLocation(BaseModel): - """The approximate location of the user.""" + """The approximate location of the user. + + If omitted or null, defaults to the + United States. To avoid this fallback, pass `{"type": "approximate"}` without + location fields. To localize results, provide the relevant location fields. + """ city: Optional[str] = None """Free text input for the city of the user, e.g. `San Francisco`.""" @@ -77,4 +82,9 @@ class BetaWebSearchTool(BaseModel): """ user_location: Optional[UserLocation] = None - """The approximate location of the user.""" + """The approximate location of the user. + + If omitted or null, defaults to the United States. To avoid this fallback, pass + `{"type": "approximate"}` without location fields. To localize results, provide + the relevant location fields. + """ diff --git a/src/openai/types/beta/beta_web_search_tool_param.py b/src/openai/types/beta/beta_web_search_tool_param.py index e3a3ae5621..1d6cdf5965 100644 --- a/src/openai/types/beta/beta_web_search_tool_param.py +++ b/src/openai/types/beta/beta_web_search_tool_param.py @@ -24,7 +24,12 @@ class Filters(TypedDict, total=False): class UserLocation(TypedDict, total=False): - """The approximate location of the user.""" + """The approximate location of the user. + + If omitted or null, defaults to the + United States. To avoid this fallback, pass `{"type": "approximate"}` without + location fields. To localize results, provide the relevant location fields. + """ city: Optional[str] """Free text input for the city of the user, e.g. `San Francisco`.""" @@ -79,4 +84,9 @@ class BetaWebSearchToolParam(TypedDict, total=False): """ user_location: Optional[UserLocation] - """The approximate location of the user.""" + """The approximate location of the user. + + If omitted or null, defaults to the United States. To avoid this fallback, pass + `{"type": "approximate"}` without location fields. To localize results, provide + the relevant location fields. + """ diff --git a/src/openai/types/responses/web_search_preview_tool.py b/src/openai/types/responses/web_search_preview_tool.py index 0de61b2821..5bd2005cbe 100644 --- a/src/openai/types/responses/web_search_preview_tool.py +++ b/src/openai/types/responses/web_search_preview_tool.py @@ -9,7 +9,10 @@ class UserLocation(BaseModel): - """The user's location.""" + """The approximate location of the user. + + If omitted or null, defaults to the United States. To avoid this fallback, pass `{"type": "approximate"}` without location fields. To localize results, provide the relevant location fields. + """ type: Literal["approximate"] """The type of location approximation. Always `approximate`.""" @@ -55,4 +58,9 @@ class WebSearchPreviewTool(BaseModel): """ user_location: Optional[UserLocation] = None - """The user's location.""" + """The approximate location of the user. + + If omitted or null, defaults to the United States. To avoid this fallback, pass + `{"type": "approximate"}` without location fields. To localize results, provide + the relevant location fields. + """ diff --git a/src/openai/types/responses/web_search_preview_tool_param.py b/src/openai/types/responses/web_search_preview_tool_param.py index d18068586a..fc4f07c394 100644 --- a/src/openai/types/responses/web_search_preview_tool_param.py +++ b/src/openai/types/responses/web_search_preview_tool_param.py @@ -9,7 +9,10 @@ class UserLocation(TypedDict, total=False): - """The user's location.""" + """The approximate location of the user. + + If omitted or null, defaults to the United States. To avoid this fallback, pass `{"type": "approximate"}` without location fields. To localize results, provide the relevant location fields. + """ type: Required[Literal["approximate"]] """The type of location approximation. Always `approximate`.""" @@ -55,4 +58,9 @@ class WebSearchPreviewToolParam(TypedDict, total=False): """ user_location: Optional[UserLocation] - """The user's location.""" + """The approximate location of the user. + + If omitted or null, defaults to the United States. To avoid this fallback, pass + `{"type": "approximate"}` without location fields. To localize results, provide + the relevant location fields. + """ diff --git a/src/openai/types/responses/web_search_tool.py b/src/openai/types/responses/web_search_tool.py index f6323e6642..bbdc014e7f 100644 --- a/src/openai/types/responses/web_search_tool.py +++ b/src/openai/types/responses/web_search_tool.py @@ -22,7 +22,12 @@ class Filters(BaseModel): class UserLocation(BaseModel): - """The approximate location of the user.""" + """The approximate location of the user. + + If omitted or null, defaults to the + United States. To avoid this fallback, pass `{"type": "approximate"}` without + location fields. To localize results, provide the relevant location fields. + """ city: Optional[str] = None """Free text input for the city of the user, e.g. `San Francisco`.""" @@ -77,4 +82,9 @@ class WebSearchTool(BaseModel): """ user_location: Optional[UserLocation] = None - """The approximate location of the user.""" + """The approximate location of the user. + + If omitted or null, defaults to the United States. To avoid this fallback, pass + `{"type": "approximate"}` without location fields. To localize results, provide + the relevant location fields. + """ diff --git a/src/openai/types/responses/web_search_tool_param.py b/src/openai/types/responses/web_search_tool_param.py index 1129babeab..1e6568a113 100644 --- a/src/openai/types/responses/web_search_tool_param.py +++ b/src/openai/types/responses/web_search_tool_param.py @@ -24,7 +24,12 @@ class Filters(TypedDict, total=False): class UserLocation(TypedDict, total=False): - """The approximate location of the user.""" + """The approximate location of the user. + + If omitted or null, defaults to the + United States. To avoid this fallback, pass `{"type": "approximate"}` without + location fields. To localize results, provide the relevant location fields. + """ city: Optional[str] """Free text input for the city of the user, e.g. `San Francisco`.""" @@ -79,4 +84,9 @@ class WebSearchToolParam(TypedDict, total=False): """ user_location: Optional[UserLocation] - """The approximate location of the user.""" + """The approximate location of the user. + + If omitted or null, defaults to the United States. To avoid this fallback, pass + `{"type": "approximate"}` without location fields. To localize results, provide + the relevant location fields. + """ diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py index 54490e133f..6116c42c18 100644 --- a/tests/test_extract_files.py +++ b/tests/test_extract_files.py @@ -41,6 +41,16 @@ def test_top_level_file_array() -> None: assert query == {"title": "hello"} +@pytest.mark.parametrize("file", [b"zip contents", ("skill.zip", b"zip contents")]) +def test_single_file_fallback_after_array_path(file: FileTypes) -> None: + query = {"files": file, "title": "hello"} + assert extract_files(query, paths=[["files", ""]]) == [] + assert query == {"files": file, "title": "hello"} + + assert extract_files(query, paths=[["files", ""], ["files"]]) == [("files", file)] + assert query == {"title": "hello"} + + @pytest.mark.parametrize( "query,paths,expected", [ diff --git a/tests/test_files.py b/tests/test_files.py index a94ddce0ef..51ca9f0acc 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -1,14 +1,49 @@ +import io +import zipfile from pathlib import Path import anyio +import httpx2 import pytest +from openai import OpenAI, AsyncOpenAI +from tests.respx2 import MockRouter from openai._files import to_httpx_files, deepcopy_with_paths, async_to_httpx_files from openai._utils import extract_files +from .conftest import base_url + readme_path = Path(__file__).parent.parent.joinpath("README.md") +@pytest.mark.respx2(base_url=base_url) +@pytest.mark.asyncio +@pytest.mark.parametrize("is_async", [False, True]) +@pytest.mark.parametrize("as_list", [False, True]) +async def test_skills_upload_zip( + client: OpenAI, async_client: AsyncOpenAI, respx2_mock: MockRouter, is_async: bool, as_list: bool +) -> None: + buffer = io.BytesIO() + with zipfile.ZipFile(buffer, "w") as archive: + archive.writestr("SKILL.md", "---\nname: example\ndescription: Test skill\n---\n# Example\n") + content = buffer.getvalue() + file = ("skill.zip", content) + files = [file] if as_list else file + respx2_mock.post("/skills").mock(return_value=httpx2.Response(200, json={})) + response = ( + await async_client.skills.with_raw_response.create(files=files) + if is_async + else client.skills.with_raw_response.create(files=files) + ) + request = response.http_request + body = await request.aread() if is_async else request.read() + assert request.headers["content-type"].startswith("multipart/form-data;") + expected_field = b'name="files[]"' if as_list else b'name="files"' + assert expected_field in body + assert b'filename="skill.zip"' in body + assert body.count(content) == 1 + + def test_pathlib_includes_file_name() -> None: result = to_httpx_files({"file": readme_path}) assert result == {"file": ("README.md", readme_path.read_bytes())} diff --git a/uv.lock b/uv.lock index fba61f3f08..0e69bdb0f9 100644 --- a/uv.lock +++ b/uv.lock @@ -1535,7 +1535,7 @@ wheels = [ [[package]] name = "openai" -version = "3.19.1" # x-release-please-version +version = "3.19.2" # x-release-please-version source = { editable = "." } dependencies = [ { name = "anyio" },