diff --git a/.castiron.stats.yml b/.castiron.stats.yml index 406734151d..5cd981abc9 100644 --- a/.castiron.stats.yml +++ b/.castiron.stats.yml @@ -1,8 +1,8 @@ schema_version: 1 -generation_id: 9fa1f60f-3df4-4d35-8b22-19dbbb6c80fb +generation_id: 18a47a5c-822b-47f7-9e5e-afd9c57dc6f6 openapi_spec_hash: 051ddca625ac3b40a3f9d7687ff56386 openapi_transformed_spec_hash: da601ee105aa5acf0deebbb9b3d87b8e -config_hash: 032fbaa89aa701b3e9a159015126f57a -codegen_sha: b345a3bfad1729f1640878441f304e9fc4787c36 -codegen_hash: f71f5997995e620542e459fbe3a23789b2fbacf139d5d0c805e79c456abd2f49 -public_codegen_sha: 75ba2ba983afb0a8823fae82670c5323c0ee9908 +config_hash: 2af89d3471277a8a879f4176a3414e0c +codegen_sha: 7e3f90acf16f400505edeff5eb14ebb11981975a +codegen_hash: d0c03ec7e35a1c576cfc7197aa8a3bd4e2b5e240ad90621c86437967444e3ef8 +public_codegen_sha: 5466ab74e52ebd7b818ac772b88a5074f114355b diff --git a/docs/castiron-generated-code-transition.md b/docs/castiron-generated-code-transition.md index b937140ba8..724c6dbafc 100644 --- a/docs/castiron-generated-code-transition.md +++ b/docs/castiron-generated-code-transition.md @@ -6,15 +6,9 @@ Eliminate handwritten patches in generated-owned files while preserving every ex No removals or renames of public symbols, new required arguments, narrower input/return types, dependency or Python-floor changes, altered defaults, new payload limits, changed warnings, or changed sync/async behavior are authorized by this transition. Deprecated methods and compatibility import paths remain supported. -## Verified integration candidate — 2026-10-22 +## Delivered integration — uploads, aliases and Live defaults -Private candidate #315 measures **32 mixed files; 5,632 additions + 1,213 deletions = 6,845 custom patch lines**, with **37,881 integrated versus 33,462 generated physical lines**. Its released baseline contains 48 mixed files / 9,173 patch lines. This is a private candidate reduction, not a merged public result. - -Evaluated SDK code: `5fcd05c9064e5bcfa820aed283d9570e1f997255`; released baseline: `49e6757e464ce03b6f47c89740feaa75870ba168`. Normal generation used merged compiler `6959b4685e426934700ebb40c159e382714c8542`, private checkpoint `b345a3bfad1729f1640878441f304e9fc4787c36`, generation `9fa1f60f-3df4-4d35-8b22-19dbbb6c80fb`, and content hash `f71f5997995e620542e459fbe3a23789b2fbacf139d5d0c805e79c456abd2f49`. The unchanged reporter verified real checkpoint ancestry and recorded content hashes with `--fetch --require-head-hash`. - -The API specification and dependency manifests/locks remain unchanged. Configuration changes remain limited to the eight upload/agent helper, alias and Live queue paths. The seven polling paths remain a separate follow-up; released polling runtime additions are preserved. No generation ownership is removed, no new mixed files are introduced, and no budget or exclusion is changed. - -16 files now match generation byte for byte, eliminating 1,838 old patch lines. The net patch reduction is 2,328; changes to the generated baseline also affect the remaining patches. These counts include comments, blank lines and moves and do not represent removed executable statements. +[#4152](https://github.com/openai/openai-python/pull/4152) merged through the normal public queue on 2026-10-22 as `a29dc3947aeccecc2e85d8fbad4d7c03b8af13e9`. This delivered **48 → 32 mixed files** and **9,173 → 6,845 custom patch lines**, a net reduction of **2,328**. Private #315 is closed as completed; its generation checkpoints are retained. The 16 files below match generation byte for byte and account for 1,838 previous patch lines; generated-baseline changes explain the difference from the net reduction. | File now matching generation | Integrated = generated lines | Previous patch | | --- | ---: | ---: | @@ -35,17 +29,15 @@ The API specification and dependency manifests/locks remain unchanged. Configura | `src/openai/types/__init__.py` | 150 | 1 | | `tests/api_resources/admin/organization/projects/test_api_keys.py` | 442 | 10 | -Remaining files whose patch size changed: +The released integration preserves upload factories, subclass dispatch, lazy defaults and logging, aliases, Live queue defaults, and current/older-runtime WebSocket behavior. Its validation included 1,606 runtime/client/security tests with 16 skips, 158 reporting tests with one skip, full lint/type checks and independent checkpoint/contract review. All review feedback was addressed before normal protected merge. [Main post-merge CI](https://github.com/openai/openai-python/actions/runs/39727147208) passed. Historical private-candidate evidence is retained under `delivered_integrations` in the JSON. -| File | Previous patch | Candidate patch | -| --- | ---: | ---: | -| `src/openai/resources/beta/responses/responses.py` | 214 | 110 | -| `src/openai/resources/realtime/realtime.py` | 134 | 12 | -| `src/openai/resources/realtime/translations/translations.py` | 138 | 52 | -| `src/openai/resources/responses/responses.py` | 1096 | 994 | -| `tests/api_resources/test_videos.py` | 88 | 12 | +## Current published baseline — 2026-10-22 -### Remaining candidate inventory +Released commit `a29dc3947aeccecc2e85d8fbad4d7c03b8af13e9` contains **32 mixed files; 5,632 additions + 1,213 deletions = 6,845 custom patch lines**, with **37,881 integrated versus 33,462 generated physical lines**. The public generated snapshot is `75ba2ba983afb0a8823fae82670c5323c0ee9908`, generation `9fa1f60f-3df4-4d35-8b22-19dbbb6c80fb`, content hash `f71f5997995e620542e459fbe3a23789b2fbacf139d5d0c805e79c456abd2f49`. The unchanged reporter verified real public checkpoint ancestry and the recorded hash with `--public --fetch --require-head-hash`; run from a clone whose origin is the public SDK, using this released SHA for both base and head to reproduce the inventory. + +The snapshot owns 1,970 paths, including 1,797 source paths. Another 205 source paths / 35,731 physical lines remain outside generated ownership. The polling candidate retains this ownership, the existing exclusions and the 10,000-line budget. Physical lines include comments and blanks; patch additions plus deletions are not a count of executable statements. The original October 19 audit below remains historical; `current_audit` records this released baseline, and `published_audit_history` retains the previous 48-file audit. + +### Current per-file inventory | File | Integrated lines | Generated lines | Added | Deleted | Patch | Track | | --- | ---: | ---: | ---: | ---: | ---: | --- | @@ -82,76 +74,51 @@ Remaining files whose patch size changed: | `src/openai/types/realtime/conversation_item_input_audio_transcription_delta_event.py` | 40 | 40 | 1 | 1 | 2 | T1 | | `src/openai/types/realtime/realtime_error_event.py` | 25 | 25 | 1 | 1 | 2 | T1 | -Machine-readable evidence is under `pending_candidate_audit` in [castiron-transition-inventory.json](castiron-transition-inventory.json). +## Verified polling integration candidate — 2026-10-22 -Validation on this exact code head: the normal SDK lint script passed Ruff, Pyright (zero errors), Mypy (2,047 files) and import verification; 1,606 runtime/client/security compatibility tests passed with 16 skips; 159 reporting tests ran successfully with one skip (158 passed). Existing public declarations and decorators are unchanged; the only added declarations are two private fallback methods. Dependency manifests, lockfile, release/version metadata, API reference, service-account documentation and the prepared translations module are byte-identical to the released baseline. Independent review verified all 1,971 manifest hashes/modes against the pure checkpoint, all canonical reporting/workflow bytes, and the unchanged eight-path configuration scope. +[Private #326](https://github.com/openai/openai-python-internal/pull/326) measures **32 → 27 mixed files** and **6,845 → 6,186 custom patch lines**, a further **659** reduction. This is a separately verified private candidate, not yet a merged public reduction. The 27 remaining files contain 4,979 additions + 1,207 deletions and 32,760 integrated versus 28,988 generated physical lines. -The generated WebSocket call sites preserve the released runtime helper and subclass dispatch, using a distinct fallback only when that optional helper is absent. Dynamic lookup survives normal formatting and typechecks with both current and older runtimes; blank-value, repeated-key and mapping semantics remain covered. Queue bootstrap policy applies isolation to every verified constituent while retaining combined-budget enforcement and approval checks. Source fixes alone do not resolve candidate review feedback; the published SDK head must include and pass both fixes. +Evaluated code: `aaa793c62214fa0c1541581aff6b4235fe5b9d24`. Normal generation used merged compiler `6959b4685e426934700ebb40c159e382714c8542`, pure checkpoint `7e3f90acf16f400505edeff5eb14ebb11981975a`, configuration checkpoint `db11a1a29f20f388bf6443d8bf5666e492f94777`, generation `18a47a5c-822b-47f7-9e5e-afd9c57dc6f6`, and content hash `d0c03ec7e35a1c576cfc7197aa8a3bd4e2b5e240ad90621c86437967444e3ef8`. The unchanged reporter (SHA-256 `6b7cfda1f85426daaa2e4f6186e2d1b20ac638dadaa54be8142ecec89fefd880`) verifies the authentic private checkpoint ancestry and recorded hash. -Reproduce the private candidate report from an internal SDK checkout containing these commits: +Exactly seven authored polling configuration settings are added to the released configuration: Files retrieve; vector-store Files create/retrieve; vector-store FileBatches create/retrieve; and Videos create/retrieve. All eight predecessor settings remain intact. Removing these seven settings and collapsing the five endpoint-only shorthand maps reproduces the complete predecessor configuration. Raw and transformed API specifications, dependency files, release metadata, handwritten runtimes, client/translation code, ownership, exclusions and budgets remain unchanged. -```sh -python3 -I scripts/castiron/custom_code_report.py report \ - --repo . \ - --base 49e6757e464ce03b6f47c89740feaa75870ba168 \ - --head 5fcd05c9064e5bcfa820aed283d9570e1f997255 \ - --fetch --require-head-hash \ - --out /tmp/python-transition-candidate-report -``` - -## Current published baseline — 2026-10-22 +The five files below now match the pure checkpoint exactly. Their 659 prior patch lines account for the full reduction; every remaining patch is unchanged. -Published baseline `49e6757e464ce03b6f47c89740feaa75870ba168` contains **48 mixed files; 7,565 additions + 1,608 deletions = 9,173 custom patch lines**, with **50,243 integrated physical lines versus 44,286 generated physical lines** in those files. The fixed October 19 audit below remains historical; `current_audit` in the JSON records this released baseline. The additive upload runtime (#4146) and polling runtime (#4148) are merged; their handwritten modules remain outside generated ownership. - -Verified public snapshot: `f99cb5bb5fc87965058d7055e382a25d1d064bcc`; generation `dd7765a4-3f6a-4a04-aaca-19aaa7507cdd`; generated-content SHA-256 `a7aa886594dd13f43e180a8516000bdf96481ed147ba52f0f2def6e243bb03b5`. The reporter is byte-identical to this published revision (SHA-256 `6b7cfda1f85426daaa2e4f6186e2d1b20ac638dadaa54be8142ecec89fefd880`); `--public --fetch --require-head-hash` verifies the actual public checkpoint ancestry and content hash. Run the report from a clone whose `origin` is the public SDK repository; set both `--base` and `--head` to this baseline SHA to reproduce the current inventory. No synthetic tracking ref or checkpoint recovery is needed. +| File now matching generation | Integrated = generated lines | Previous patch | +| --- | ---: | ---: | +| `src/openai/resources/files.py` | 826 | 26 | +| `src/openai/resources/vector_stores/file_batches.py` | 776 | 245 | +| `src/openai/resources/vector_stores/files.py` | 987 | 221 | +| `src/openai/resources/videos.py` | 1444 | 155 | +| `tests/api_resources/test_videos.py` | 939 | 12 | -Release 3.36.1 (#4145) changes only version/changelog metadata after #4151 and leaves every inventory row and total unchanged. This baseline includes the released service-account API-key documentation update in [#4151](https://github.com/openai/openai-python/pull/4151). Compared with `7cd702895379656bee62047ff64813ff616aeef7`, changed generated baselines add two mixed files: `scripts/castiron/custom_code_report.py` (206 patch lines) and `src/openai/types/__init__.py` (1 patch line). Existing mixed-file patches increase by another 903 lines: 1,110 additional measured patch lines overall. The SDK update itself changes documentation, while its generated checkpoint changes this measurement. The current 10,000-line budget has 827 lines of headroom. Preserve released behavior, exports and tests; measure the pending candidate separately before claiming a reduction. +Validation: **303 polling/runtime compatibility tests passed** across synchronous, asynchronous and Trio behavior; the normal lint script passed Ruff, Pyright (zero errors), Mypy (2,047 files) and import verification. All **112 public facade contracts** and **16 runtime exports** are retained. Independent review verified that the ten delegate runtime bodies match released method bodies after receiver/validator-alias normalization, every argument is forwarded unchanged, and all other method bodies (including video polling) remain unchanged. Deadlines, validation-before-side-effects, cancellation, upload ownership, concurrency, headers, deprecation warnings and terminal-state behavior remain covered. -The snapshot owns 1,968 paths, including 1,797 source paths. Another 205 paths under `src/openai/` (35,728 physical lines) remain outside generated ownership and are not mixed files. Physical lines include comments and blanks. Additions plus deletions measure patch size, not executable handwritten statements removed. +Local dependency freshness checks fail on both the unchanged released predecessor and this candidate under the managed registry. Local checks use the exact committed dependency set installed with `uv sync --frozen --all-extras` and no re-resolution; registry security settings and lockfiles are retained. Current hosted locked-install checks remain required before promotion. SDK CODEOWNER review and normal public checks/queue are also required; the independent audit does not replace them. -### Current per-file inventory +### Remaining candidate inventory | File | Integrated lines | Generated lines | Added | Deleted | Patch | Track | | --- | ---: | ---: | ---: | ---: | ---: | --- | | `src/openai/resources/beta/threads/runs/runs.py` | 3161 | 2052 | 1290 | 181 | 1471 | T4 | -| `src/openai/resources/responses/responses.py` | 5171 | 4295 | 986 | 110 | 1096 | T5 | +| `src/openai/resources/responses/responses.py` | 5181 | 4335 | 920 | 74 | 994 | T5 | | `src/openai/resources/audio/transcriptions.py` | 1089 | 1403 | 287 | 601 | 888 | T3 | -| `src/openai/_client.py` | 2401 | 1702 | 759 | 60 | 819 | T2 | -| `scripts/castiron/test_custom_code_report.py` | 1081 | 460 | 621 | 0 | 621 | T0 | +| `src/openai/_client.py` | 2440 | 1741 | 759 | 60 | 819 | T2 | | `src/openai/resources/chat/completions/completions.py` | 3456 | 2854 | 603 | 1 | 604 | T6 | | `src/openai/resources/beta/threads/threads.py` | 2009 | 1626 | 390 | 7 | 397 | T4 | -| `src/openai/resources/vector_stores/file_batches.py` | 867 | 628 | 242 | 3 | 245 | T7 | -| `src/openai/resources/uploads/uploads.py` | 765 | 544 | 221 | 0 | 221 | T8 | -| `src/openai/resources/vector_stores/files.py` | 1032 | 813 | 220 | 1 | 221 | T7 | -| `src/openai/resources/beta/responses/responses.py` | 5565 | 5535 | 122 | 92 | 214 | T5 | -| `scripts/castiron/custom_code_report.py` | 1250 | 1078 | 189 | 17 | 206 | T0 | | `src/openai/__init__.py` | 505 | 321 | 185 | 1 | 186 | T2 | -| `src/openai/resources/live/sideband.py` | 1413 | 1394 | 89 | 70 | 159 | T11 | -| `src/openai/resources/videos.py` | 1457 | 1306 | 153 | 2 | 155 | T10 | -| `src/openai/resources/live/forks.py` | 1446 | 1417 | 85 | 56 | 141 | T11 | -| `src/openai/resources/realtime/translations/translations.py` | 1152 | 1094 | 98 | 40 | 138 | T11 | -| `src/openai/resources/realtime/realtime.py` | 1650 | 1624 | 80 | 54 | 134 | T11 | | `api.md` | 2090 | 1979 | 119 | 8 | 127 | T16 | -| `src/openai/resources/beta/agents/environments/files.py` | 596 | 473 | 125 | 2 | 127 | T9 | -| `src/openai/resources/live/live.py` | 1643 | 1606 | 81 | 44 | 125 | T11 | | `src/openai/resources/beta/agents/sessions/sessions.py` | 1304 | 1202 | 113 | 11 | 124 | T9 | | `src/openai/resources/audio/translations.py` | 374 | 409 | 40 | 75 | 115 | T3 | +| `src/openai/resources/beta/responses/responses.py` | 5575 | 5575 | 55 | 55 | 110 | T5 | | `src/openai/resources/webhooks/webhooks.py` | 1146 | 1061 | 89 | 4 | 93 | T12 | -| `tests/api_resources/test_videos.py` | 939 | 919 | 54 | 34 | 88 | T10 | | `src/openai/resources/webhooks/__init__.py` | 42 | 73 | 24 | 55 | 79 | T12 | -| `scripts/castiron/test_custom_code_budget.py` | 773 | 711 | 66 | 4 | 70 | T0 | -| `scripts/castiron/custom_code_budget.py` | 758 | 737 | 41 | 20 | 61 | T0 | +| `src/openai/resources/realtime/translations/translations.py` | 1152 | 1136 | 34 | 18 | 52 | T11 | | `src/openai/resources/embeddings.py` | 263 | 257 | 27 | 21 | 48 | T13 | -| `scripts/castiron/test_custom_code_github.py` | 653 | 622 | 31 | 0 | 31 | T0 | -| `scripts/castiron/test_custom_code_publication.py` | 567 | 542 | 26 | 1 | 27 | T0 | -| `src/openai/resources/files.py` | 826 | 800 | 26 | 0 | 26 | T14 | -| `src/openai/resources/beta/beta.py` | 269 | 248 | 21 | 0 | 21 | T15 | | `src/openai/resources/realtime/api.md` | 149 | 169 | 0 | 20 | 20 | T16 | +| `src/openai/resources/realtime/realtime.py` | 1660 | 1654 | 9 | 3 | 12 | T11 | | `src/openai/types/decision_input_image_param.py` | 47 | 37 | 11 | 1 | 12 | T1 | | `src/openai/types/decision_input_text_param.py` | 33 | 23 | 11 | 1 | 12 | T1 | -| `src/openai/resources/beta/agents/sessions/artifacts.py` | 561 | 551 | 10 | 0 | 10 | T9 | -| `tests/api_resources/admin/organization/projects/test_api_keys.py` | 442 | 432 | 10 | 0 | 10 | T1 | -| `scripts/castiron/fixtures/github_publisher.cjs` | 64 | 59 | 6 | 1 | 7 | T0 | | `src/openai/types/webhooks/__init__.py` | 73 | 71 | 3 | 1 | 4 | T1 | | `src/openai/types/webhooks/unwrap_webhook_event.py` | 74 | 74 | 2 | 2 | 4 | T1 | | `src/openai/types/responses/tool.py` | 398 | 399 | 1 | 2 | 3 | T1 | @@ -160,7 +127,17 @@ The snapshot owns 1,968 paths, including 1,797 source paths. Another 205 paths u | `src/openai/types/fine_tuning/fine_tuning_job_integration.py` | 5 | 7 | 0 | 2 | 2 | T1 | | `src/openai/types/realtime/conversation_item_input_audio_transcription_delta_event.py` | 40 | 40 | 1 | 1 | 2 | T1 | | `src/openai/types/realtime/realtime_error_event.py` | 25 | 25 | 1 | 1 | 2 | T1 | -| `src/openai/types/__init__.py` | 150 | 149 | 1 | 0 | 1 | T2 | + +Machine-readable pending evidence is under `pending_candidate_audit` in [castiron-transition-inventory.json](castiron-transition-inventory.json). Reproduce from an internal SDK checkout containing the evaluated commit: + +```sh +python3 -I scripts/castiron/custom_code_report.py report \ + --repo . \ + --base a29dc3947aeccecc2e85d8fbad4d7c03b8af13e9 \ + --head aaa793c62214fa0c1541581aff6b4235fe5b9d24 \ + --fetch --require-head-hash \ + --out /tmp/python-polling-candidate-report +``` ## Baseline and measurement @@ -465,16 +442,16 @@ If one successor needs a human review, CI repair, or another merged prerequisite | Work | State | Evidence / next action | | --- | --- | --- | | Baseline audit and plan | Merged; post-merge checks green / complete | [#4114](https://github.com/openai/openai-python/pull/4114), head `7eea9bf1beb552f4db8c456105b6a28bfe9bebe3`, merged as `6a52987f2da5c3bc9b700a7094c0cf14fe7e78dc` on main. Frozen audit: 41 files / 7,368 patch lines at `5589146d`; current merged reduction recorded separately. Explicit current-head human approval and protected merge gates were satisfied. Post-merge push CI, CodeQL and release checks are green on the merge commit, including [run 39316813847](https://github.com/openai/openai-python/actions/runs/39316813847). | -| T0 generated reporting restoration | Original cleanup merged; renewed formatting work remains | [#4113](https://github.com/openai/openai-python/pull/4113), head `886eb8079e65cfc0509fc945c17d7578b3459bfb`, merged as `9d12b565554c08bcee6287c0d8f671b8c97ed911` on main. Both files matched the verified generated snapshot byte for byte at that merge; its post-merge measurement was 39 mixed files / 7,351 patch lines, with all other customizations unchanged. Prior validation: 94 offline tests passed, one compiler-binary contract test skipped (binary unset); AST comparison and assertion mutation passed. Conductor verified every post-merge push check successful on the merge commit, including [run 39315896992](https://github.com/openai/openai-python/actions/runs/39315896992). | +| T0 generated reporting restoration | Original cleanup and renewed restoration merged | Original [#4113](https://github.com/openai/openai-python/pull/4113) remains historical. The renewed canonical reporting restoration is delivered in [#4152](https://github.com/openai/openai-python/pull/4152), merge a29dc394; all seven reporting files listed above match generation. | | T8 upload helper contract coverage | Merged; successor eligible | [#4112](https://github.com/openai/openai-python/pull/4112) merged on 2026-10-19 as `163a2966140bc0399af7d81e03580d9d907a51df`. Preserve its 16 sync/async contracts in the typed upload-delegation successor. The default 64 MiB boundary remains an additional coverage requirement. This test-only merge does not reduce mixed-file counts or approve a later upload migration. | | T10 video polling compatibility | Merged; successor eligible | [#4115](https://github.com/openai/openai-python/pull/4115) merged on 2026-10-19 as `969ae2c734a508ebc5e3370c9dc41bd15602a4a4`. Retain all 21 public-entrypoint sync/async contracts and the interval mutation evidence in the polling-family successor. This test-only merge does not reduce mixed-file counts. | -| T15 Beta resource alias generation | Compiler merged; candidate SDK integration verified | [Delta 2566635](https://flow.apps.openai.org/deltas/openai/openai/2566635) merged as `509f49c1`. Candidate #315 removes the 21-line Beta patch and preserves the existing sync/async cached-property contracts and collision guards. Public promotion and its normal checks remain. | -| T11 Live queue generation | Compiler merged; candidate SDK integration verified | [Delta 2565610](https://flow.apps.openai.org/deltas/openai/openai/2565610) is merged. Candidate #315 makes Live, forks and sideband byte-identical to generated output, retaining the byte limit, direct constructors and caller-owned empty queues. | +| T15 Beta resource alias generation | Compiler and SDK integration merged | [Delta 2566635](https://flow.apps.openai.org/deltas/openai/openai/2566635) merged as 509f49c1. [#4152](https://github.com/openai/openai-python/pull/4152) delivers the generated aliases, removing the 21-line Beta patch while preserving sync/async cached properties and collision guards. | +| T11 Live queue generation | Compiler and SDK integration merged | [Delta 2565610](https://flow.apps.openai.org/deltas/openai/openai/2565610) and [#4152](https://github.com/openai/openai-python/pull/4152) deliver generation for Live, forks and sideband, retaining byte limits, direct constructors and caller-owned empty queues. All three files match generation. | | T1 authored schema documentation | Redundant candidate; merged source already present | [Delta 2565621](https://flow.apps.openai.org/deltas/openai/openai/2565621) duplicates the corrections already merged through [Delta 1490194](https://flow.apps.openai.org/deltas/openai/openai/1490194), commit `97ba353f886c5248912eb50a516cb1aab2fee4c6`. The duplicate is closed with supersession evidence. The generated/integrated SDK descriptions still differ at the current audit; verify input propagation and regenerate before claiming the two-file/four-line reduction. | -| T7 / T10 / T14 polling family | Compiler and runtime preparation merged; separate integration preview verified | [Delta 2565776](https://flow.apps.openai.org/deltas/openai/openai/2565776) merged as `7098c3c1a28c8ab3afebd9158a68518243e059ce`. The ten vector runtime exports and parity tests are released in [#4148](https://github.com/openai/openai-python/pull/4148), merge `7cd702895379656bee62047ff64813ff616aeef7`. Seven polling configuration paths remain excluded from #315. The isolated successor preview preserves all 112 public contracts and 16 exports and passes 303 tests, Ruff, Mypy and Pyright. Regenerate against the final predecessor, publish, and verify its authoritative inventory before claiming a reduction. | -| T8 / T9 upload and agent facades | Compiler and upload runtime merged; candidate SDK integration verified | [Delta 2568634](https://flow.apps.openai.org/deltas/openai/openai/2568634) merged as `14c8b579`; upload runtime preparation [#4146](https://github.com/openai/openai-python/pull/4146) merged as `2d58e828`. Candidate #315 clears uploads, environment files and session artifacts while retaining synchronous factories on async resources, subclass dispatch, lazy defaults, logger timing, part ordering and cleanup. Its focused suite includes the 64 MiB boundary. | -| T0 recurring reporting formatting | Candidate cleanup verified; public integration pending | [Delta 2585899](https://flow.apps.openai.org/deltas/openai/openai/2585899) supplies the canonical reporting templates. Follow-up [Delta 2588691](https://flow.apps.openai.org/deltas/openai/openai/2588691) merged as `697c482a7e50c9278c03d25b9d0862e2f2e5d71e`, preserving executable semantics while fixing shared-template formatting. [Delta 2591310](https://flow.apps.openai.org/deltas/openai/openai/2591310) also preserves the requested unittest import style through normal formatting. The current candidate clears 7 reporting files / 1,023 baseline patch lines, as itemized above. Budget and exclusions remain unchanged. | -| T11 client WebSocket query composition | Compiler fixes merged; regenerated candidate verified | [Delta 2589816](https://flow.apps.openai.org/deltas/openai/openai/2589816) preserves released query serialization; [Delta 2590767](https://flow.apps.openai.org/deltas/openai/openai/2590767) preserves runtime helper dispatch with a distinct older-runtime fallback and formatter-safe dynamic lookup. Candidate #315 retains client properties and the prepared translations query path. Current/older-runtime typing and released wire contracts must remain green through public promotion. Client and translations files remain mixed. | +| T7 / T10 / T14 polling family | Compiler/runtime preparation merged; private integration verified | [Delta 2565776](https://flow.apps.openai.org/deltas/openai/openai/2565776) and runtime [#4148](https://github.com/openai/openai-python/pull/4148) are merged. [Private #326](https://github.com/openai/openai-python-internal/pull/326) applies exactly seven settings on released a29dc394; five files / 659 patch lines clear, 112 contracts and 16 exports remain, and 303 tests pass. Public promotion/merge remain pending. | +| T8 / T9 upload and agent facades | Compiler, runtime and SDK integration merged | [Delta 2568634](https://flow.apps.openai.org/deltas/openai/openai/2568634), runtime [#4146](https://github.com/openai/openai-python/pull/4146) and integration [#4152](https://github.com/openai/openai-python/pull/4152) are merged. Uploads, environment files and session artifacts match generation; synchronous factories on async resources, subclass dispatch, lazy defaults/logger timing, ordering, cleanup and the 64 MiB boundary remain covered. The separate sessions facade still has 124 patch lines. | +| T0 recurring reporting formatting | Canonical source and public integration merged | [Deltas 2588691](https://flow.apps.openai.org/deltas/openai/openai/2588691) and [2591310](https://flow.apps.openai.org/deltas/openai/openai/2591310) fix shared-template formatting and preserve the requested unittest import style. [#4152](https://github.com/openai/openai-python/pull/4152) clears seven reporting files / 1,023 prior patch lines. Budget and exclusions are unchanged. | +| T11 client WebSocket query composition | Compiler fixes and public integration merged | [Deltas 2589816](https://flow.apps.openai.org/deltas/openai/openai/2589816) and [2590767](https://flow.apps.openai.org/deltas/openai/openai/2590767) are delivered in [#4152](https://github.com/openai/openai-python/pull/4152). Released query semantics, runtime/subclass dispatch, older-runtime fallback, client properties and prepared translations remain verified. Client and translations files still have unrelated custom patches. | | Earlier stable web-search naming | Superseded; stale candidate closed | [Internal #55](https://github.com/openai/openai-python-internal/pull/55) closed after confirming merged [Delta 2513050](https://flow.apps.openai.org/deltas/openai/openai/2513050) and exact generated/integrated equality of both stable web-search modules on current main. Do not reintroduce its stale generation metadata or historical cross-language naming change. | The ledger reports observed work; projected reductions are not merged reductions. Refresh it with PR/Delta links and exact tested heads as publication completes. diff --git a/docs/castiron-transition-inventory.json b/docs/castiron-transition-inventory.json index 0d5cf5de0c..31ab9fbcf8 100644 --- a/docs/castiron-transition-inventory.json +++ b/docs/castiron-transition-inventory.json @@ -455,19 +455,19 @@ ], "current_audit": { "audit_date": "2026-10-22", - "sdk_commit": "49e6757e464ce03b6f47c89740feaa75870ba168", - "public_codegen_commit": "f99cb5bb5fc87965058d7055e382a25d1d064bcc", - "generation_id": "dd7765a4-3f6a-4a04-aaca-19aaa7507cdd", - "codegen_hash": "a7aa886594dd13f43e180a8516000bdf96481ed147ba52f0f2def6e243bb03b5", + "sdk_commit": "a29dc3947aeccecc2e85d8fbad4d7c03b8af13e9", + "public_codegen_commit": "75ba2ba983afb0a8823fae82670c5323c0ee9908", + "generation_id": "9fa1f60f-3df4-4d35-8b22-19dbbb6c80fb", + "codegen_hash": "f71f5997995e620542e459fbe3a23789b2fbacf139d5d0c805e79c456abd2f49", "reporter_sha256": "6b7cfda1f85426daaa2e4f6186e2d1b20ac638dadaa54be8142ecec89fefd880", "measurement": "generated-owned paths; git diff --numstat --no-renames; additions plus deletions; physical lines include comments and blanks", "totals": { - "mixed_files": 48, - "added_lines": 7565, - "deleted_lines": 1608, - "custom_patch_lines": 9173, - "current_physical_lines": 50243, - "generated_physical_lines": 44286 + "added": 5632, + "removed": 1213, + "patch": 6845, + "current_lines": 37881, + "generated_lines": 33462, + "mixed_files": 32 }, "files": [ { @@ -482,12 +482,12 @@ }, { "path": "src/openai/resources/responses/responses.py", - "added": 986, - "removed": 110, - "patch": 1096, + "added": 920, + "removed": 74, + "patch": 994, "present": true, - "current_lines": 5171, - "generated_lines": 4295, + "current_lines": 5181, + "generated_lines": 4335, "track": "T5" }, { @@ -506,20 +506,10 @@ "removed": 60, "patch": 819, "present": true, - "current_lines": 2401, - "generated_lines": 1702, + "current_lines": 2440, + "generated_lines": 1741, "track": "T2" }, - { - "path": "scripts/castiron/test_custom_code_report.py", - "added": 621, - "removed": 0, - "patch": 621, - "present": true, - "current_lines": 1081, - "generated_lines": 460, - "track": "T0" - }, { "path": "src/openai/resources/chat/completions/completions.py", "added": 603, @@ -550,16 +540,6 @@ "generated_lines": 628, "track": "T7" }, - { - "path": "src/openai/resources/uploads/uploads.py", - "added": 221, - "removed": 0, - "patch": 221, - "present": true, - "current_lines": 765, - "generated_lines": 544, - "track": "T8" - }, { "path": "src/openai/resources/vector_stores/files.py", "added": 220, @@ -570,26 +550,6 @@ "generated_lines": 813, "track": "T7" }, - { - "path": "src/openai/resources/beta/responses/responses.py", - "added": 122, - "removed": 92, - "patch": 214, - "present": true, - "current_lines": 5565, - "generated_lines": 5535, - "track": "T5" - }, - { - "path": "scripts/castiron/custom_code_report.py", - "added": 189, - "removed": 17, - "patch": 206, - "present": true, - "current_lines": 1250, - "generated_lines": 1078, - "track": "T0" - }, { "path": "src/openai/__init__.py", "added": 185, @@ -600,16 +560,6 @@ "generated_lines": 321, "track": "T2" }, - { - "path": "src/openai/resources/live/sideband.py", - "added": 89, - "removed": 70, - "patch": 159, - "present": true, - "current_lines": 1413, - "generated_lines": 1394, - "track": "T11" - }, { "path": "src/openai/resources/videos.py", "added": 153, @@ -620,36 +570,6 @@ "generated_lines": 1306, "track": "T10" }, - { - "path": "src/openai/resources/live/forks.py", - "added": 85, - "removed": 56, - "patch": 141, - "present": true, - "current_lines": 1446, - "generated_lines": 1417, - "track": "T11" - }, - { - "path": "src/openai/resources/realtime/translations/translations.py", - "added": 98, - "removed": 40, - "patch": 138, - "present": true, - "current_lines": 1152, - "generated_lines": 1094, - "track": "T11" - }, - { - "path": "src/openai/resources/realtime/realtime.py", - "added": 80, - "removed": 54, - "patch": 134, - "present": true, - "current_lines": 1650, - "generated_lines": 1624, - "track": "T11" - }, { "path": "api.md", "added": 119, @@ -660,26 +580,6 @@ "generated_lines": 1979, "track": "T16" }, - { - "path": "src/openai/resources/beta/agents/environments/files.py", - "added": 125, - "removed": 2, - "patch": 127, - "present": true, - "current_lines": 596, - "generated_lines": 473, - "track": "T9" - }, - { - "path": "src/openai/resources/live/live.py", - "added": 81, - "removed": 44, - "patch": 125, - "present": true, - "current_lines": 1643, - "generated_lines": 1606, - "track": "T11" - }, { "path": "src/openai/resources/beta/agents/sessions/sessions.py", "added": 113, @@ -700,6 +600,16 @@ "generated_lines": 409, "track": "T3" }, + { + "path": "src/openai/resources/beta/responses/responses.py", + "added": 55, + "removed": 55, + "patch": 110, + "present": true, + "current_lines": 5575, + "generated_lines": 5575, + "track": "T5" + }, { "path": "src/openai/resources/webhooks/webhooks.py", "added": 89, @@ -710,16 +620,6 @@ "generated_lines": 1061, "track": "T12" }, - { - "path": "tests/api_resources/test_videos.py", - "added": 54, - "removed": 34, - "patch": 88, - "present": true, - "current_lines": 939, - "generated_lines": 919, - "track": "T10" - }, { "path": "src/openai/resources/webhooks/__init__.py", "added": 24, @@ -731,24 +631,14 @@ "track": "T12" }, { - "path": "scripts/castiron/test_custom_code_budget.py", - "added": 66, - "removed": 4, - "patch": 70, - "present": true, - "current_lines": 773, - "generated_lines": 711, - "track": "T0" - }, - { - "path": "scripts/castiron/custom_code_budget.py", - "added": 41, - "removed": 20, - "patch": 61, + "path": "src/openai/resources/realtime/translations/translations.py", + "added": 34, + "removed": 18, + "patch": 52, "present": true, - "current_lines": 758, - "generated_lines": 737, - "track": "T0" + "current_lines": 1152, + "generated_lines": 1136, + "track": "T11" }, { "path": "src/openai/resources/embeddings.py", @@ -760,26 +650,6 @@ "generated_lines": 257, "track": "T13" }, - { - "path": "scripts/castiron/test_custom_code_github.py", - "added": 31, - "removed": 0, - "patch": 31, - "present": true, - "current_lines": 653, - "generated_lines": 622, - "track": "T0" - }, - { - "path": "scripts/castiron/test_custom_code_publication.py", - "added": 26, - "removed": 1, - "patch": 27, - "present": true, - "current_lines": 567, - "generated_lines": 542, - "track": "T0" - }, { "path": "src/openai/resources/files.py", "added": 26, @@ -790,16 +660,6 @@ "generated_lines": 800, "track": "T14" }, - { - "path": "src/openai/resources/beta/beta.py", - "added": 21, - "removed": 0, - "patch": 21, - "present": true, - "current_lines": 269, - "generated_lines": 248, - "track": "T15" - }, { "path": "src/openai/resources/realtime/api.md", "added": 0, @@ -810,6 +670,16 @@ "generated_lines": 169, "track": "T16" }, + { + "path": "src/openai/resources/realtime/realtime.py", + "added": 9, + "removed": 3, + "patch": 12, + "present": true, + "current_lines": 1660, + "generated_lines": 1654, + "track": "T11" + }, { "path": "src/openai/types/decision_input_image_param.py", "added": 11, @@ -831,34 +701,14 @@ "track": "T1" }, { - "path": "src/openai/resources/beta/agents/sessions/artifacts.py", - "added": 10, - "removed": 0, - "patch": 10, - "present": true, - "current_lines": 561, - "generated_lines": 551, - "track": "T9" - }, - { - "path": "tests/api_resources/admin/organization/projects/test_api_keys.py", - "added": 10, + "path": "tests/api_resources/test_videos.py", + "added": 12, "removed": 0, - "patch": 10, - "present": true, - "current_lines": 442, - "generated_lines": 432, - "track": "T1" - }, - { - "path": "scripts/castiron/fixtures/github_publisher.cjs", - "added": 6, - "removed": 1, - "patch": 7, + "patch": 12, "present": true, - "current_lines": 64, - "generated_lines": 59, - "track": "T0" + "current_lines": 939, + "generated_lines": 927, + "track": "T10" }, { "path": "src/openai/types/webhooks/__init__.py", @@ -939,39 +789,29 @@ "current_lines": 25, "generated_lines": 25, "track": "T1" - }, - { - "path": "src/openai/types/__init__.py", - "added": 1, - "removed": 0, - "patch": 1, - "present": true, - "current_lines": 150, - "generated_lines": 149, - "track": "T2" } ], "ownership": { - "generated_paths": 1968, + "generated_paths": 1970, "generated_source_paths": 1797, "excluded_source_paths": 205, - "excluded_source_lines": 35728 + "excluded_source_lines": 35731 } }, "pending_candidate_audit": { "audit_date": "2026-10-22", - "sdk_commit": "5fcd05c9064e5bcfa820aed283d9570e1f997255", - "generation_id": "9fa1f60f-3df4-4d35-8b22-19dbbb6c80fb", - "codegen_hash": "f71f5997995e620542e459fbe3a23789b2fbacf139d5d0c805e79c456abd2f49", + "sdk_commit": "aaa793c62214fa0c1541581aff6b4235fe5b9d24", + "generation_id": "18a47a5c-822b-47f7-9e5e-afd9c57dc6f6", + "codegen_hash": "d0c03ec7e35a1c576cfc7197aa8a3bd4e2b5e240ad90621c86437967444e3ef8", "reporter_sha256": "6b7cfda1f85426daaa2e4f6186e2d1b20ac638dadaa54be8142ecec89fefd880", - "measurement": "Verified private generated checkpoint versus integrated candidate. Physical lines count all blob.splitlines(); patch lines count additions plus deletions, not executable handwritten statements.", + "measurement": "generated-owned paths; git diff --numstat --no-renames; additions plus deletions; physical lines include comments and blanks", "totals": { - "added": 5632, - "removed": 1213, - "patch": 6845, - "current_lines": 37881, - "generated_lines": 33462, - "mixed_files": 32 + "added": 4979, + "removed": 1207, + "patch": 6186, + "current_lines": 32760, + "generated_lines": 28988, + "mixed_files": 27 }, "files": [ { @@ -1034,26 +874,6 @@ "generated_lines": 1626, "track": "T4" }, - { - "path": "src/openai/resources/vector_stores/file_batches.py", - "added": 242, - "removed": 3, - "patch": 245, - "present": true, - "current_lines": 867, - "generated_lines": 628, - "track": "T7" - }, - { - "path": "src/openai/resources/vector_stores/files.py", - "added": 220, - "removed": 1, - "patch": 221, - "present": true, - "current_lines": 1032, - "generated_lines": 813, - "track": "T7" - }, { "path": "src/openai/__init__.py", "added": 185, @@ -1064,16 +884,6 @@ "generated_lines": 321, "track": "T2" }, - { - "path": "src/openai/resources/videos.py", - "added": 153, - "removed": 2, - "patch": 155, - "present": true, - "current_lines": 1457, - "generated_lines": 1306, - "track": "T10" - }, { "path": "api.md", "added": 119, @@ -1154,16 +964,6 @@ "generated_lines": 257, "track": "T13" }, - { - "path": "src/openai/resources/files.py", - "added": 26, - "removed": 0, - "patch": 26, - "present": true, - "current_lines": 826, - "generated_lines": 800, - "track": "T14" - }, { "path": "src/openai/resources/realtime/api.md", "added": 0, @@ -1204,16 +1004,6 @@ "generated_lines": 23, "track": "T1" }, - { - "path": "tests/api_resources/test_videos.py", - "added": 12, - "removed": 0, - "patch": 12, - "present": true, - "current_lines": 939, - "generated_lines": 927, - "track": "T10" - }, { "path": "src/openai/types/webhooks/__init__.py", "added": 3, @@ -1301,146 +1091,1052 @@ "excluded_source_paths": 205, "excluded_source_lines": 35731 }, - "private_codegen_commit": "b345a3bfad1729f1640878441f304e9fc4787c36", + "private_codegen_commit": "7e3f90acf16f400505edeff5eb14ebb11981975a", "status": "private candidate; not a merged public reduction", - "pull_request": "https://github.com/openai/openai-python-internal/pull/315", - "base_sdk_commit": "49e6757e464ce03b6f47c89740feaa75870ba168", + "pull_request": "https://github.com/openai/openai-python-internal/pull/326", + "base_sdk_commit": "a29dc3947aeccecc2e85d8fbad4d7c03b8af13e9", "compiler_commit": "6959b4685e426934700ebb40c159e382714c8542", + "configuration_checkpoint": "db11a1a29f20f388bf6443d8bf5666e492f94777", "verification": { "public": false, "fetch": true, "require_head_hash": true, "counts": { - "after": 32, - "baseline_changed": 22, - "before": 48, - "unchanged": 26 + "after": 27, + "baseline_changed": 5, + "before": 32, + "unchanged": 27 } }, "removed_files": [ { - "path": "scripts/castiron/custom_code_budget.py", - "previous_patch": 61, - "current_lines": 702, - "generated_lines": 702 - }, - { - "path": "scripts/castiron/custom_code_report.py", - "previous_patch": 206, - "current_lines": 1250, - "generated_lines": 1250 - }, - { - "path": "scripts/castiron/fixtures/github_publisher.cjs", - "previous_patch": 7, - "current_lines": 69, - "generated_lines": 69 - }, - { - "path": "scripts/castiron/test_custom_code_budget.py", - "previous_patch": 70, - "current_lines": 773, - "generated_lines": 773 - }, - { - "path": "scripts/castiron/test_custom_code_github.py", - "previous_patch": 31, - "current_lines": 978, - "generated_lines": 978 - }, - { - "path": "scripts/castiron/test_custom_code_publication.py", - "previous_patch": 27, - "current_lines": 873, - "generated_lines": 873 - }, - { - "path": "scripts/castiron/test_custom_code_report.py", - "previous_patch": 621, - "current_lines": 1084, - "generated_lines": 1084 - }, - { - "path": "src/openai/resources/beta/agents/environments/files.py", - "previous_patch": 127, - "current_lines": 604, - "generated_lines": 604 - }, - { - "path": "src/openai/resources/beta/agents/sessions/artifacts.py", - "previous_patch": 10, - "current_lines": 573, - "generated_lines": 573 - }, - { - "path": "src/openai/resources/beta/beta.py", - "previous_patch": 21, - "current_lines": 266, - "generated_lines": 266 - }, - { - "path": "src/openai/resources/live/forks.py", - "previous_patch": 141, - "current_lines": 1460, - "generated_lines": 1460 - }, - { - "path": "src/openai/resources/live/live.py", - "previous_patch": 125, - "current_lines": 1649, - "generated_lines": 1649 + "path": "src/openai/resources/files.py", + "previous_patch": 26, + "current_lines": 826, + "generated_lines": 826 }, { - "path": "src/openai/resources/live/sideband.py", - "previous_patch": 159, - "current_lines": 1427, - "generated_lines": 1427 + "path": "src/openai/resources/vector_stores/file_batches.py", + "previous_patch": 245, + "current_lines": 776, + "generated_lines": 776 }, { - "path": "src/openai/resources/uploads/uploads.py", + "path": "src/openai/resources/vector_stores/files.py", "previous_patch": 221, - "current_lines": 652, - "generated_lines": 652 + "current_lines": 987, + "generated_lines": 987 }, { - "path": "src/openai/types/__init__.py", - "previous_patch": 1, - "current_lines": 150, - "generated_lines": 150 + "path": "src/openai/resources/videos.py", + "previous_patch": 155, + "current_lines": 1444, + "generated_lines": 1444 }, { - "path": "tests/api_resources/admin/organization/projects/test_api_keys.py", - "previous_patch": 10, - "current_lines": 442, - "generated_lines": 442 + "path": "tests/api_resources/test_videos.py", + "previous_patch": 12, + "current_lines": 939, + "generated_lines": 939 } ], - "changed_remaining_patches": [ - { - "path": "src/openai/resources/beta/responses/responses.py", - "before": 214, - "after": 110 - }, - { - "path": "src/openai/resources/realtime/realtime.py", - "before": 134, - "after": 12 - }, - { - "path": "src/openai/resources/realtime/translations/translations.py", - "before": 138, - "after": 52 - }, - { - "path": "src/openai/resources/responses/responses.py", - "before": 1096, - "after": 994 - }, - { - "path": "tests/api_resources/test_videos.py", - "before": 88, - "after": 12 + "changed_remaining_patches": [] + }, + "delivered_integrations": [ + { + "audit_date": "2026-10-22", + "sdk_commit": "a29dc3947aeccecc2e85d8fbad4d7c03b8af13e9", + "generation_id": "9fa1f60f-3df4-4d35-8b22-19dbbb6c80fb", + "codegen_hash": "f71f5997995e620542e459fbe3a23789b2fbacf139d5d0c805e79c456abd2f49", + "reporter_sha256": "6b7cfda1f85426daaa2e4f6186e2d1b20ac638dadaa54be8142ecec89fefd880", + "measurement": "Verified private generated checkpoint versus integrated candidate. Physical lines count all blob.splitlines(); patch lines count additions plus deletions, not executable handwritten statements.", + "totals": { + "added": 5632, + "removed": 1213, + "patch": 6845, + "current_lines": 37881, + "generated_lines": 33462, + "mixed_files": 32 + }, + "files": [ + { + "path": "src/openai/resources/beta/threads/runs/runs.py", + "added": 1290, + "removed": 181, + "patch": 1471, + "present": true, + "current_lines": 3161, + "generated_lines": 2052, + "track": "T4" + }, + { + "path": "src/openai/resources/responses/responses.py", + "added": 920, + "removed": 74, + "patch": 994, + "present": true, + "current_lines": 5181, + "generated_lines": 4335, + "track": "T5" + }, + { + "path": "src/openai/resources/audio/transcriptions.py", + "added": 287, + "removed": 601, + "patch": 888, + "present": true, + "current_lines": 1089, + "generated_lines": 1403, + "track": "T3" + }, + { + "path": "src/openai/_client.py", + "added": 759, + "removed": 60, + "patch": 819, + "present": true, + "current_lines": 2440, + "generated_lines": 1741, + "track": "T2" + }, + { + "path": "src/openai/resources/chat/completions/completions.py", + "added": 603, + "removed": 1, + "patch": 604, + "present": true, + "current_lines": 3456, + "generated_lines": 2854, + "track": "T6" + }, + { + "path": "src/openai/resources/beta/threads/threads.py", + "added": 390, + "removed": 7, + "patch": 397, + "present": true, + "current_lines": 2009, + "generated_lines": 1626, + "track": "T4" + }, + { + "path": "src/openai/resources/vector_stores/file_batches.py", + "added": 242, + "removed": 3, + "patch": 245, + "present": true, + "current_lines": 867, + "generated_lines": 628, + "track": "T7" + }, + { + "path": "src/openai/resources/vector_stores/files.py", + "added": 220, + "removed": 1, + "patch": 221, + "present": true, + "current_lines": 1032, + "generated_lines": 813, + "track": "T7" + }, + { + "path": "src/openai/__init__.py", + "added": 185, + "removed": 1, + "patch": 186, + "present": true, + "current_lines": 505, + "generated_lines": 321, + "track": "T2" + }, + { + "path": "src/openai/resources/videos.py", + "added": 153, + "removed": 2, + "patch": 155, + "present": true, + "current_lines": 1457, + "generated_lines": 1306, + "track": "T10" + }, + { + "path": "api.md", + "added": 119, + "removed": 8, + "patch": 127, + "present": true, + "current_lines": 2090, + "generated_lines": 1979, + "track": "T16" + }, + { + "path": "src/openai/resources/beta/agents/sessions/sessions.py", + "added": 113, + "removed": 11, + "patch": 124, + "present": true, + "current_lines": 1304, + "generated_lines": 1202, + "track": "T9" + }, + { + "path": "src/openai/resources/audio/translations.py", + "added": 40, + "removed": 75, + "patch": 115, + "present": true, + "current_lines": 374, + "generated_lines": 409, + "track": "T3" + }, + { + "path": "src/openai/resources/beta/responses/responses.py", + "added": 55, + "removed": 55, + "patch": 110, + "present": true, + "current_lines": 5575, + "generated_lines": 5575, + "track": "T5" + }, + { + "path": "src/openai/resources/webhooks/webhooks.py", + "added": 89, + "removed": 4, + "patch": 93, + "present": true, + "current_lines": 1146, + "generated_lines": 1061, + "track": "T12" + }, + { + "path": "src/openai/resources/webhooks/__init__.py", + "added": 24, + "removed": 55, + "patch": 79, + "present": true, + "current_lines": 42, + "generated_lines": 73, + "track": "T12" + }, + { + "path": "src/openai/resources/realtime/translations/translations.py", + "added": 34, + "removed": 18, + "patch": 52, + "present": true, + "current_lines": 1152, + "generated_lines": 1136, + "track": "T11" + }, + { + "path": "src/openai/resources/embeddings.py", + "added": 27, + "removed": 21, + "patch": 48, + "present": true, + "current_lines": 263, + "generated_lines": 257, + "track": "T13" + }, + { + "path": "src/openai/resources/files.py", + "added": 26, + "removed": 0, + "patch": 26, + "present": true, + "current_lines": 826, + "generated_lines": 800, + "track": "T14" + }, + { + "path": "src/openai/resources/realtime/api.md", + "added": 0, + "removed": 20, + "patch": 20, + "present": true, + "current_lines": 149, + "generated_lines": 169, + "track": "T16" + }, + { + "path": "src/openai/resources/realtime/realtime.py", + "added": 9, + "removed": 3, + "patch": 12, + "present": true, + "current_lines": 1660, + "generated_lines": 1654, + "track": "T11" + }, + { + "path": "src/openai/types/decision_input_image_param.py", + "added": 11, + "removed": 1, + "patch": 12, + "present": true, + "current_lines": 47, + "generated_lines": 37, + "track": "T1" + }, + { + "path": "src/openai/types/decision_input_text_param.py", + "added": 11, + "removed": 1, + "patch": 12, + "present": true, + "current_lines": 33, + "generated_lines": 23, + "track": "T1" + }, + { + "path": "tests/api_resources/test_videos.py", + "added": 12, + "removed": 0, + "patch": 12, + "present": true, + "current_lines": 939, + "generated_lines": 927, + "track": "T10" + }, + { + "path": "src/openai/types/webhooks/__init__.py", + "added": 3, + "removed": 1, + "patch": 4, + "present": true, + "current_lines": 73, + "generated_lines": 71, + "track": "T1" + }, + { + "path": "src/openai/types/webhooks/unwrap_webhook_event.py", + "added": 2, + "removed": 2, + "patch": 4, + "present": true, + "current_lines": 74, + "generated_lines": 74, + "track": "T1" + }, + { + "path": "src/openai/types/responses/tool.py", + "added": 1, + "removed": 2, + "patch": 3, + "present": true, + "current_lines": 398, + "generated_lines": 399, + "track": "T1" + }, + { + "path": "src/openai/types/responses/tool_param.py", + "added": 3, + "removed": 0, + "patch": 3, + "present": true, + "current_lines": 398, + "generated_lines": 395, + "track": "T1" + }, + { + "path": "src/openai/types/webhooks/live_call_incoming_webhook_event.py", + "added": 2, + "removed": 1, + "patch": 3, + "present": true, + "current_lines": 71, + "generated_lines": 70, + "track": "T1" + }, + { + "path": "src/openai/types/fine_tuning/fine_tuning_job_integration.py", + "added": 0, + "removed": 2, + "patch": 2, + "present": true, + "current_lines": 5, + "generated_lines": 7, + "track": "T1" + }, + { + "path": "src/openai/types/realtime/conversation_item_input_audio_transcription_delta_event.py", + "added": 1, + "removed": 1, + "patch": 2, + "present": true, + "current_lines": 40, + "generated_lines": 40, + "track": "T1" + }, + { + "path": "src/openai/types/realtime/realtime_error_event.py", + "added": 1, + "removed": 1, + "patch": 2, + "present": true, + "current_lines": 25, + "generated_lines": 25, + "track": "T1" + } + ], + "ownership": { + "generated_paths": 1970, + "generated_source_paths": 1797, + "excluded_source_paths": 205, + "excluded_source_lines": 35731 + }, + "private_codegen_commit": "b345a3bfad1729f1640878441f304e9fc4787c36", + "status": "merged public reduction", + "pull_request": "https://github.com/openai/openai-python/pull/4152", + "base_sdk_commit": "49e6757e464ce03b6f47c89740feaa75870ba168", + "compiler_commit": "6959b4685e426934700ebb40c159e382714c8542", + "verification": { + "public": true, + "fetch": true, + "require_head_hash": true, + "counts": { + "after": 32, + "baseline_changed": 22, + "before": 48, + "unchanged": 26 + } + }, + "removed_files": [ + { + "path": "scripts/castiron/custom_code_budget.py", + "previous_patch": 61, + "current_lines": 702, + "generated_lines": 702 + }, + { + "path": "scripts/castiron/custom_code_report.py", + "previous_patch": 206, + "current_lines": 1250, + "generated_lines": 1250 + }, + { + "path": "scripts/castiron/fixtures/github_publisher.cjs", + "previous_patch": 7, + "current_lines": 69, + "generated_lines": 69 + }, + { + "path": "scripts/castiron/test_custom_code_budget.py", + "previous_patch": 70, + "current_lines": 773, + "generated_lines": 773 + }, + { + "path": "scripts/castiron/test_custom_code_github.py", + "previous_patch": 31, + "current_lines": 978, + "generated_lines": 978 + }, + { + "path": "scripts/castiron/test_custom_code_publication.py", + "previous_patch": 27, + "current_lines": 873, + "generated_lines": 873 + }, + { + "path": "scripts/castiron/test_custom_code_report.py", + "previous_patch": 621, + "current_lines": 1084, + "generated_lines": 1084 + }, + { + "path": "src/openai/resources/beta/agents/environments/files.py", + "previous_patch": 127, + "current_lines": 604, + "generated_lines": 604 + }, + { + "path": "src/openai/resources/beta/agents/sessions/artifacts.py", + "previous_patch": 10, + "current_lines": 573, + "generated_lines": 573 + }, + { + "path": "src/openai/resources/beta/beta.py", + "previous_patch": 21, + "current_lines": 266, + "generated_lines": 266 + }, + { + "path": "src/openai/resources/live/forks.py", + "previous_patch": 141, + "current_lines": 1460, + "generated_lines": 1460 + }, + { + "path": "src/openai/resources/live/live.py", + "previous_patch": 125, + "current_lines": 1649, + "generated_lines": 1649 + }, + { + "path": "src/openai/resources/live/sideband.py", + "previous_patch": 159, + "current_lines": 1427, + "generated_lines": 1427 + }, + { + "path": "src/openai/resources/uploads/uploads.py", + "previous_patch": 221, + "current_lines": 652, + "generated_lines": 652 + }, + { + "path": "src/openai/types/__init__.py", + "previous_patch": 1, + "current_lines": 150, + "generated_lines": 150 + }, + { + "path": "tests/api_resources/admin/organization/projects/test_api_keys.py", + "previous_patch": 10, + "current_lines": 442, + "generated_lines": 442 + } + ], + "changed_remaining_patches": [ + { + "path": "src/openai/resources/beta/responses/responses.py", + "before": 214, + "after": 110 + }, + { + "path": "src/openai/resources/realtime/realtime.py", + "before": 134, + "after": 12 + }, + { + "path": "src/openai/resources/realtime/translations/translations.py", + "before": 138, + "after": 52 + }, + { + "path": "src/openai/resources/responses/responses.py", + "before": 1096, + "after": 994 + }, + { + "path": "tests/api_resources/test_videos.py", + "before": 88, + "after": 12 + } + ], + "private_candidate_code_commit": "5fcd05c9064e5bcfa820aed283d9570e1f997255", + "private_pull_request": "https://github.com/openai/openai-python-internal/pull/315", + "public_codegen_commit": "75ba2ba983afb0a8823fae82670c5323c0ee9908" + } + ], + "published_audit_history": [ + { + "audit_date": "2026-10-22", + "sdk_commit": "49e6757e464ce03b6f47c89740feaa75870ba168", + "public_codegen_commit": "f99cb5bb5fc87965058d7055e382a25d1d064bcc", + "generation_id": "dd7765a4-3f6a-4a04-aaca-19aaa7507cdd", + "codegen_hash": "a7aa886594dd13f43e180a8516000bdf96481ed147ba52f0f2def6e243bb03b5", + "reporter_sha256": "6b7cfda1f85426daaa2e4f6186e2d1b20ac638dadaa54be8142ecec89fefd880", + "measurement": "generated-owned paths; git diff --numstat --no-renames; additions plus deletions; physical lines include comments and blanks", + "totals": { + "mixed_files": 48, + "added_lines": 7565, + "deleted_lines": 1608, + "custom_patch_lines": 9173, + "current_physical_lines": 50243, + "generated_physical_lines": 44286 + }, + "files": [ + { + "path": "src/openai/resources/beta/threads/runs/runs.py", + "added": 1290, + "removed": 181, + "patch": 1471, + "present": true, + "current_lines": 3161, + "generated_lines": 2052, + "track": "T4" + }, + { + "path": "src/openai/resources/responses/responses.py", + "added": 986, + "removed": 110, + "patch": 1096, + "present": true, + "current_lines": 5171, + "generated_lines": 4295, + "track": "T5" + }, + { + "path": "src/openai/resources/audio/transcriptions.py", + "added": 287, + "removed": 601, + "patch": 888, + "present": true, + "current_lines": 1089, + "generated_lines": 1403, + "track": "T3" + }, + { + "path": "src/openai/_client.py", + "added": 759, + "removed": 60, + "patch": 819, + "present": true, + "current_lines": 2401, + "generated_lines": 1702, + "track": "T2" + }, + { + "path": "scripts/castiron/test_custom_code_report.py", + "added": 621, + "removed": 0, + "patch": 621, + "present": true, + "current_lines": 1081, + "generated_lines": 460, + "track": "T0" + }, + { + "path": "src/openai/resources/chat/completions/completions.py", + "added": 603, + "removed": 1, + "patch": 604, + "present": true, + "current_lines": 3456, + "generated_lines": 2854, + "track": "T6" + }, + { + "path": "src/openai/resources/beta/threads/threads.py", + "added": 390, + "removed": 7, + "patch": 397, + "present": true, + "current_lines": 2009, + "generated_lines": 1626, + "track": "T4" + }, + { + "path": "src/openai/resources/vector_stores/file_batches.py", + "added": 242, + "removed": 3, + "patch": 245, + "present": true, + "current_lines": 867, + "generated_lines": 628, + "track": "T7" + }, + { + "path": "src/openai/resources/uploads/uploads.py", + "added": 221, + "removed": 0, + "patch": 221, + "present": true, + "current_lines": 765, + "generated_lines": 544, + "track": "T8" + }, + { + "path": "src/openai/resources/vector_stores/files.py", + "added": 220, + "removed": 1, + "patch": 221, + "present": true, + "current_lines": 1032, + "generated_lines": 813, + "track": "T7" + }, + { + "path": "src/openai/resources/beta/responses/responses.py", + "added": 122, + "removed": 92, + "patch": 214, + "present": true, + "current_lines": 5565, + "generated_lines": 5535, + "track": "T5" + }, + { + "path": "scripts/castiron/custom_code_report.py", + "added": 189, + "removed": 17, + "patch": 206, + "present": true, + "current_lines": 1250, + "generated_lines": 1078, + "track": "T0" + }, + { + "path": "src/openai/__init__.py", + "added": 185, + "removed": 1, + "patch": 186, + "present": true, + "current_lines": 505, + "generated_lines": 321, + "track": "T2" + }, + { + "path": "src/openai/resources/live/sideband.py", + "added": 89, + "removed": 70, + "patch": 159, + "present": true, + "current_lines": 1413, + "generated_lines": 1394, + "track": "T11" + }, + { + "path": "src/openai/resources/videos.py", + "added": 153, + "removed": 2, + "patch": 155, + "present": true, + "current_lines": 1457, + "generated_lines": 1306, + "track": "T10" + }, + { + "path": "src/openai/resources/live/forks.py", + "added": 85, + "removed": 56, + "patch": 141, + "present": true, + "current_lines": 1446, + "generated_lines": 1417, + "track": "T11" + }, + { + "path": "src/openai/resources/realtime/translations/translations.py", + "added": 98, + "removed": 40, + "patch": 138, + "present": true, + "current_lines": 1152, + "generated_lines": 1094, + "track": "T11" + }, + { + "path": "src/openai/resources/realtime/realtime.py", + "added": 80, + "removed": 54, + "patch": 134, + "present": true, + "current_lines": 1650, + "generated_lines": 1624, + "track": "T11" + }, + { + "path": "api.md", + "added": 119, + "removed": 8, + "patch": 127, + "present": true, + "current_lines": 2090, + "generated_lines": 1979, + "track": "T16" + }, + { + "path": "src/openai/resources/beta/agents/environments/files.py", + "added": 125, + "removed": 2, + "patch": 127, + "present": true, + "current_lines": 596, + "generated_lines": 473, + "track": "T9" + }, + { + "path": "src/openai/resources/live/live.py", + "added": 81, + "removed": 44, + "patch": 125, + "present": true, + "current_lines": 1643, + "generated_lines": 1606, + "track": "T11" + }, + { + "path": "src/openai/resources/beta/agents/sessions/sessions.py", + "added": 113, + "removed": 11, + "patch": 124, + "present": true, + "current_lines": 1304, + "generated_lines": 1202, + "track": "T9" + }, + { + "path": "src/openai/resources/audio/translations.py", + "added": 40, + "removed": 75, + "patch": 115, + "present": true, + "current_lines": 374, + "generated_lines": 409, + "track": "T3" + }, + { + "path": "src/openai/resources/webhooks/webhooks.py", + "added": 89, + "removed": 4, + "patch": 93, + "present": true, + "current_lines": 1146, + "generated_lines": 1061, + "track": "T12" + }, + { + "path": "tests/api_resources/test_videos.py", + "added": 54, + "removed": 34, + "patch": 88, + "present": true, + "current_lines": 939, + "generated_lines": 919, + "track": "T10" + }, + { + "path": "src/openai/resources/webhooks/__init__.py", + "added": 24, + "removed": 55, + "patch": 79, + "present": true, + "current_lines": 42, + "generated_lines": 73, + "track": "T12" + }, + { + "path": "scripts/castiron/test_custom_code_budget.py", + "added": 66, + "removed": 4, + "patch": 70, + "present": true, + "current_lines": 773, + "generated_lines": 711, + "track": "T0" + }, + { + "path": "scripts/castiron/custom_code_budget.py", + "added": 41, + "removed": 20, + "patch": 61, + "present": true, + "current_lines": 758, + "generated_lines": 737, + "track": "T0" + }, + { + "path": "src/openai/resources/embeddings.py", + "added": 27, + "removed": 21, + "patch": 48, + "present": true, + "current_lines": 263, + "generated_lines": 257, + "track": "T13" + }, + { + "path": "scripts/castiron/test_custom_code_github.py", + "added": 31, + "removed": 0, + "patch": 31, + "present": true, + "current_lines": 653, + "generated_lines": 622, + "track": "T0" + }, + { + "path": "scripts/castiron/test_custom_code_publication.py", + "added": 26, + "removed": 1, + "patch": 27, + "present": true, + "current_lines": 567, + "generated_lines": 542, + "track": "T0" + }, + { + "path": "src/openai/resources/files.py", + "added": 26, + "removed": 0, + "patch": 26, + "present": true, + "current_lines": 826, + "generated_lines": 800, + "track": "T14" + }, + { + "path": "src/openai/resources/beta/beta.py", + "added": 21, + "removed": 0, + "patch": 21, + "present": true, + "current_lines": 269, + "generated_lines": 248, + "track": "T15" + }, + { + "path": "src/openai/resources/realtime/api.md", + "added": 0, + "removed": 20, + "patch": 20, + "present": true, + "current_lines": 149, + "generated_lines": 169, + "track": "T16" + }, + { + "path": "src/openai/types/decision_input_image_param.py", + "added": 11, + "removed": 1, + "patch": 12, + "present": true, + "current_lines": 47, + "generated_lines": 37, + "track": "T1" + }, + { + "path": "src/openai/types/decision_input_text_param.py", + "added": 11, + "removed": 1, + "patch": 12, + "present": true, + "current_lines": 33, + "generated_lines": 23, + "track": "T1" + }, + { + "path": "src/openai/resources/beta/agents/sessions/artifacts.py", + "added": 10, + "removed": 0, + "patch": 10, + "present": true, + "current_lines": 561, + "generated_lines": 551, + "track": "T9" + }, + { + "path": "tests/api_resources/admin/organization/projects/test_api_keys.py", + "added": 10, + "removed": 0, + "patch": 10, + "present": true, + "current_lines": 442, + "generated_lines": 432, + "track": "T1" + }, + { + "path": "scripts/castiron/fixtures/github_publisher.cjs", + "added": 6, + "removed": 1, + "patch": 7, + "present": true, + "current_lines": 64, + "generated_lines": 59, + "track": "T0" + }, + { + "path": "src/openai/types/webhooks/__init__.py", + "added": 3, + "removed": 1, + "patch": 4, + "present": true, + "current_lines": 73, + "generated_lines": 71, + "track": "T1" + }, + { + "path": "src/openai/types/webhooks/unwrap_webhook_event.py", + "added": 2, + "removed": 2, + "patch": 4, + "present": true, + "current_lines": 74, + "generated_lines": 74, + "track": "T1" + }, + { + "path": "src/openai/types/responses/tool.py", + "added": 1, + "removed": 2, + "patch": 3, + "present": true, + "current_lines": 398, + "generated_lines": 399, + "track": "T1" + }, + { + "path": "src/openai/types/responses/tool_param.py", + "added": 3, + "removed": 0, + "patch": 3, + "present": true, + "current_lines": 398, + "generated_lines": 395, + "track": "T1" + }, + { + "path": "src/openai/types/webhooks/live_call_incoming_webhook_event.py", + "added": 2, + "removed": 1, + "patch": 3, + "present": true, + "current_lines": 71, + "generated_lines": 70, + "track": "T1" + }, + { + "path": "src/openai/types/fine_tuning/fine_tuning_job_integration.py", + "added": 0, + "removed": 2, + "patch": 2, + "present": true, + "current_lines": 5, + "generated_lines": 7, + "track": "T1" + }, + { + "path": "src/openai/types/realtime/conversation_item_input_audio_transcription_delta_event.py", + "added": 1, + "removed": 1, + "patch": 2, + "present": true, + "current_lines": 40, + "generated_lines": 40, + "track": "T1" + }, + { + "path": "src/openai/types/realtime/realtime_error_event.py", + "added": 1, + "removed": 1, + "patch": 2, + "present": true, + "current_lines": 25, + "generated_lines": 25, + "track": "T1" + }, + { + "path": "src/openai/types/__init__.py", + "added": 1, + "removed": 0, + "patch": 1, + "present": true, + "current_lines": 150, + "generated_lines": 149, + "track": "T2" + } + ], + "ownership": { + "generated_paths": 1968, + "generated_source_paths": 1797, + "excluded_source_paths": 205, + "excluded_source_lines": 35728 } - ] - } + } + ] } diff --git a/scripts/castiron/generated-files.json b/scripts/castiron/generated-files.json index 2e09fa5e40..d7fd5cc575 100644 --- a/scripts/castiron/generated-files.json +++ b/scripts/castiron/generated-files.json @@ -543,7 +543,7 @@ }, "src/openai/resources/files.py": { "mode": "100644", - "sha256": "0629d0cd6e09883f185a459dea2d86a35d72c3960e462e29c89854ae680bccf4" + "sha256": "4d07e7fd7e821a92b4e55d985336c0a2b4495aa7b682b82dd1f37557171c807a" }, "src/openai/resources/fine_tuning/__init__.py": { "mode": "100644", @@ -735,11 +735,11 @@ }, "src/openai/resources/vector_stores/file_batches.py": { "mode": "100644", - "sha256": "63a7f2d37951dca32762d626fef547e1492ddd1e2adbd4a56d40ead8e3b25e98" + "sha256": "da1b1f55d74fd6c7ad6d9c980b16d41b4fb51c50db15c84e71002f0860e4c6f1" }, "src/openai/resources/vector_stores/files.py": { "mode": "100644", - "sha256": "ef82de91391256f56d50c9ce084d6954241101e0e663c5e8142ec063dc4a8293" + "sha256": "c16228370423aa83e41db874ae577b85442290acf51eca4e4937e1cfa174a7ab" }, "src/openai/resources/vector_stores/vector_stores.py": { "mode": "100644", @@ -747,7 +747,7 @@ }, "src/openai/resources/videos.py": { "mode": "100644", - "sha256": "c17aa6cdb4691bb88e69e7288a72f4e963c121a9429de61fdb1e4842168b9d0c" + "sha256": "d505c37b6faae6cb3de9dabab651edd2ea5ee8eeac2134a0db94bf568f3a77c8" }, "src/openai/resources/webhooks/__init__.py": { "mode": "100644", @@ -7843,7 +7843,7 @@ }, "tests/api_resources/test_videos.py": { "mode": "100644", - "sha256": "3be3adea04934a991e9906d794468a0e33116dd1a2ca3762b360870a626f82fc" + "sha256": "bcc55dc2c7b47d6998ac977b4b6b073ce62bd6f963496f312ff350e9ee1382c9" }, "tests/api_resources/test_webhooks.py": { "mode": "100644", diff --git a/src/openai/resources/files.py b/src/openai/resources/files.py index db2e77c2fc..963856acec 100644 --- a/src/openai/resources/files.py +++ b/src/openai/resources/files.py @@ -373,7 +373,7 @@ def wait_for_processing( poll_interval: float = 5.0, max_wait_seconds: float = 30 * 60, ) -> FileObject: - """Waits for the given file to be processed, default timeout is 30 mins.""" + "Waits for the given file to be processed, default timeout is 30 mins." return _wait_for_file_processing(self, id, poll_interval=poll_interval, max_wait_seconds=max_wait_seconds) @@ -714,7 +714,7 @@ async def wait_for_processing( poll_interval: float = 5.0, max_wait_seconds: float = 30 * 60, ) -> FileObject: - """Waits for the given file to be processed, default timeout is 30 mins.""" + "Waits for the given file to be processed, default timeout is 30 mins." return await _async_wait_for_file_processing( self, id, poll_interval=poll_interval, max_wait_seconds=max_wait_seconds ) diff --git a/src/openai/resources/vector_stores/file_batches.py b/src/openai/resources/vector_stores/file_batches.py index 8ba0201d35..87d796a1c2 100644 --- a/src/openai/resources/vector_stores/file_batches.py +++ b/src/openai/resources/vector_stores/file_batches.py @@ -2,27 +2,37 @@ from __future__ import annotations -import asyncio -from typing import Dict, Iterable, Optional -from typing_extensions import Union, Literal -from concurrent.futures import Future, ThreadPoolExecutor, as_completed +from typing import Dict, Union, Iterable, Optional +from typing_extensions import Literal import httpx2 -import sniffio from ... import _legacy_response from ...types import FileChunkingStrategyParam -from ..._types import Body, Omit, Query, Headers, NotGiven, FileTypes, SequenceNotStr, omit, not_given +from ..._types import ( + Body, + Omit, + Query, + Headers, + NotGiven, + FileTypes, + SequenceNotStr, + omit, + not_given, +) from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncCursorPage, AsyncCursorPage from ..._base_client import AsyncPaginator, make_request_options -from ...types.file_object import FileObject from ...lib._vector_stores import ( poll_vector_store_file_batch as _poll_vector_store_file_batch, async_poll_vector_store_file_batch as _async_poll_vector_store_file_batch, + create_and_poll_vector_store_file_batch as _create_and_poll_vector_store_file_batch, + upload_and_poll_vector_store_file_batch as _upload_and_poll_vector_store_file_batch, + async_create_and_poll_vector_store_file_batch as _async_create_and_poll_vector_store_file_batch, + async_upload_and_poll_vector_store_file_batch as _async_upload_and_poll_vector_store_file_batch, ) from ...types.vector_stores import file_batch_create_params, file_batch_list_files_params from ...types.file_chunking_strategy_param import FileChunkingStrategyParam @@ -218,41 +228,6 @@ def cancel( cast_to=VectorStoreFileBatch, ) - def create_and_poll( - self, - vector_store_id: str, - *, - attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, - chunking_strategy: FileChunkingStrategyParam | Omit = omit, - file_ids: SequenceNotStr[str] | Omit = omit, - files: Iterable[file_batch_create_params.File] | Omit = omit, - poll_interval_ms: int | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - 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( - vector_store_id=vector_store_id, - attributes=attributes, - chunking_strategy=chunking_strategy, - file_ids=file_ids, - files=files, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - # TODO: don't poll unless necessary?? - return self.poll( - batch.id, - vector_store_id=vector_store_id, - poll_interval_ms=poll_interval_ms, - ) - def list_files( self, batch_id: str, @@ -332,23 +307,33 @@ def list_files( model=VectorStoreFile, ) - def poll( + def create_and_poll( self, - batch_id: str, - *, vector_store_id: str, + *, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, + file_ids: SequenceNotStr[str] | Omit = omit, + files: Iterable[file_batch_create_params.File] | Omit = omit, poll_interval_ms: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: - """Wait for the given file batch to be processed. - - Note: this will return even if one of the files failed to process, you need to - check batch.file_counts.failed_count to handle this case. - """ - return _poll_vector_store_file_batch( + "Create a vector store batch and poll until all files have been processed." + return _create_and_poll_vector_store_file_batch( self, - batch_id, - vector_store_id=vector_store_id, + vector_store_id, + attributes=attributes, + chunking_strategy=chunking_strategy, + file_ids=file_ids, + files=files, poll_interval_ms=poll_interval_ms, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, ) def upload_and_poll( @@ -361,45 +346,31 @@ def upload_and_poll( poll_interval_ms: int | Omit = omit, chunking_strategy: FileChunkingStrategyParam | Omit = omit, ) -> VectorStoreFileBatch: - """Uploads the given files concurrently and then creates a vector store file batch. - - If you've already uploaded certain files that you want to include in this batch - then you can pass their IDs through the `file_ids` argument. - - By default, if any file upload fails then an exception will be eagerly raised. - - The number of concurrency uploads is configurable using the `max_concurrency` - parameter. + "Uploads the given files concurrently and then creates a vector store file batch.\n\n If you've already uploaded certain files that you want to include in this batch\n then you can pass their IDs through the `file_ids` argument.\n\n By default, if any file upload fails then an exception will be eagerly raised.\n\n The number of concurrency uploads is configurable using the `max_concurrency`\n parameter.\n\n Note: this method only supports `asyncio` or `trio` as the backing async\n runtime.\n" + return _upload_and_poll_vector_store_file_batch( + self, + vector_store_id, + files=files, + max_concurrency=max_concurrency, + file_ids=file_ids, + poll_interval_ms=poll_interval_ms, + chunking_strategy=chunking_strategy, + ) - Note: this method only supports `asyncio` or `trio` as the backing async - runtime. - """ - results: list[FileObject] = [] - - with ThreadPoolExecutor(max_workers=max_concurrency) as executor: - futures: list[Future[FileObject]] = [ - executor.submit( - self._client.files.create, - file=file, - purpose="assistants", - ) - for file in files - ] - - for future in as_completed(futures): - exc = future.exception() - if exc: - raise exc - - results.append(future.result()) - - batch = self.create_and_poll( + def poll( + self, + batch_id: str, + *, + vector_store_id: str, + poll_interval_ms: int | Omit = omit, + ) -> VectorStoreFileBatch: + "Wait for the given file batch to be processed.\n\n Note: this will return even if one of the files failed to process, you need to\n check batch.file_counts.failed_count to handle this case.\n" + return _poll_vector_store_file_batch( + self, + batch_id, vector_store_id=vector_store_id, - file_ids=[*file_ids, *(f.id for f in results)], poll_interval_ms=poll_interval_ms, - chunking_strategy=chunking_strategy, ) - return batch class AsyncFileBatches(AsyncAPIResource): @@ -588,41 +559,6 @@ async def cancel( cast_to=VectorStoreFileBatch, ) - async def create_and_poll( - self, - vector_store_id: str, - *, - attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, - chunking_strategy: FileChunkingStrategyParam | Omit = omit, - file_ids: SequenceNotStr[str] | Omit = omit, - files: Iterable[file_batch_create_params.File] | Omit = omit, - poll_interval_ms: int | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - 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( - vector_store_id=vector_store_id, - attributes=attributes, - chunking_strategy=chunking_strategy, - file_ids=file_ids, - files=files, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - # TODO: don't poll unless necessary?? - return await self.poll( - batch.id, - vector_store_id=vector_store_id, - poll_interval_ms=poll_interval_ms, - ) - def list_files( self, batch_id: str, @@ -702,23 +638,33 @@ def list_files( model=VectorStoreFile, ) - async def poll( + async def create_and_poll( self, - batch_id: str, - *, vector_store_id: str, + *, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, + file_ids: SequenceNotStr[str] | Omit = omit, + files: Iterable[file_batch_create_params.File] | Omit = omit, poll_interval_ms: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: - """Wait for the given file batch to be processed. - - Note: this will return even if one of the files failed to process, you need to - check batch.file_counts.failed_count to handle this case. - """ - return await _async_poll_vector_store_file_batch( + "Create a vector store batch and poll until all files have been processed." + return await _async_create_and_poll_vector_store_file_batch( self, - batch_id, - vector_store_id=vector_store_id, + vector_store_id, + attributes=attributes, + chunking_strategy=chunking_strategy, + file_ids=file_ids, + files=files, poll_interval_ms=poll_interval_ms, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, ) async def upload_and_poll( @@ -731,68 +677,31 @@ async def upload_and_poll( poll_interval_ms: int | Omit = omit, chunking_strategy: FileChunkingStrategyParam | Omit = omit, ) -> VectorStoreFileBatch: - """Uploads the given files concurrently and then creates a vector store file batch. - - If you've already uploaded certain files that you want to include in this batch - then you can pass their IDs through the `file_ids` argument. - - By default, if any file upload fails then an exception will be eagerly raised. - - The number of concurrency uploads is configurable using the `max_concurrency` - parameter. - - Note: this method only supports `asyncio` or `trio` as the backing async - runtime. - """ - uploaded_files: list[FileObject] = [] - - async_library = sniffio.current_async_library() - - if async_library == "asyncio": - - async def asyncio_upload_file(semaphore: asyncio.Semaphore, file: FileTypes) -> None: - async with semaphore: - file_obj = await self._client.files.create( - file=file, - purpose="assistants", - ) - uploaded_files.append(file_obj) - - semaphore = asyncio.Semaphore(max_concurrency) - - tasks = [asyncio_upload_file(semaphore, file) for file in files] - - await asyncio.gather(*tasks) - elif async_library == "trio": - # We only import if the library is being used. - # We support Python 3.7 so are using an older version of trio that does not have type information - import trio # type: ignore # pyright: ignore[reportMissingTypeStubs] - - async def trio_upload_file(limiter: trio.CapacityLimiter, file: FileTypes) -> None: - async with limiter: - file_obj = await self._client.files.create( - file=file, - purpose="assistants", - ) - uploaded_files.append(file_obj) - - limiter = trio.CapacityLimiter(max_concurrency) - - async with trio.open_nursery() as nursery: - for file in files: - nursery.start_soon(trio_upload_file, limiter, file) # pyright: ignore [reportUnknownMemberType] - else: - raise RuntimeError( - f"Async runtime {async_library} is not supported yet. Only asyncio or trio is supported", - ) + "Uploads the given files concurrently and then creates a vector store file batch.\n\n If you've already uploaded certain files that you want to include in this batch\n then you can pass their IDs through the `file_ids` argument.\n\n By default, if any file upload fails then an exception will be eagerly raised.\n\n The number of concurrency uploads is configurable using the `max_concurrency`\n parameter.\n\n Note: this method only supports `asyncio` or `trio` as the backing async\n runtime.\n" + return await _async_upload_and_poll_vector_store_file_batch( + self, + vector_store_id, + files=files, + max_concurrency=max_concurrency, + file_ids=file_ids, + poll_interval_ms=poll_interval_ms, + chunking_strategy=chunking_strategy, + ) - batch = await self.create_and_poll( + async def poll( + self, + batch_id: str, + *, + vector_store_id: str, + poll_interval_ms: int | Omit = omit, + ) -> VectorStoreFileBatch: + "Wait for the given file batch to be processed.\n\n Note: this will return even if one of the files failed to process, you need to\n check batch.file_counts.failed_count to handle this case.\n" + return await _async_poll_vector_store_file_batch( + self, + batch_id, vector_store_id=vector_store_id, - file_ids=[*file_ids, *(f.id for f in uploaded_files)], poll_interval_ms=poll_interval_ms, - chunking_strategy=chunking_strategy, ) - return batch class FileBatchesWithRawResponse: diff --git a/src/openai/resources/vector_stores/files.py b/src/openai/resources/vector_stores/files.py index 4f18f96f39..6ff85ed431 100644 --- a/src/openai/resources/vector_stores/files.py +++ b/src/openai/resources/vector_stores/files.py @@ -18,8 +18,13 @@ from ..._base_client import AsyncPaginator, make_request_options from ...lib._vector_stores import ( poll_vector_store_file as _poll_vector_store_file, - validate_max_wait_seconds as _validate_max_wait_seconds, + upload_vector_store_file as _upload_vector_store_file, async_poll_vector_store_file as _async_poll_vector_store_file, + async_upload_vector_store_file as _async_upload_vector_store_file, + create_and_poll_vector_store_file as _create_and_poll_vector_store_file, + upload_and_poll_vector_store_file as _upload_and_poll_vector_store_file, + async_create_and_poll_vector_store_file as _async_create_and_poll_vector_store_file, + async_upload_and_poll_vector_store_file as _async_upload_and_poll_vector_store_file, ) from ...types.vector_stores import file_list_params, file_create_params, file_update_params from ...types.file_chunking_strategy_param import FileChunkingStrategyParam @@ -329,70 +334,79 @@ def delete( cast_to=VectorStoreFileDeleted, ) - def create_and_poll( + def content( self, file_id: str, *, vector_store_id: str, - attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, - poll_interval_ms: int | Omit = omit, - max_wait_seconds: float | Omit = omit, - chunking_strategy: FileChunkingStrategyParam | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx2.Timeout | None | NotGiven = not_given, - ) -> VectorStoreFile: - """Attach a file to the given vector store and wait for it to be processed. - - `max_wait_seconds` limits polling after attachment; see `poll` for details. + ) -> SyncPage[FileContentResponse]: """ - _validate_max_wait_seconds(max_wait_seconds) - self.create( - vector_store_id=vector_store_id, - file_id=file_id, - chunking_strategy=chunking_strategy, - attributes=attributes, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) + Retrieve the parsed contents of a vector store file. - return self.poll( - file_id, - vector_store_id=vector_store_id, - poll_interval_ms=poll_interval_ms, - max_wait_seconds=max_wait_seconds, + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not vector_store_id: + raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} + return self._get_api_list( + path_template( + "/vector_stores/{vector_store_id}/files/{file_id}/content", + vector_store_id=vector_store_id, + file_id=file_id, + ), + page=SyncPage[FileContentResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + model=FileContentResponse, ) - def poll( + def create_and_poll( self, file_id: str, *, vector_store_id: str, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, poll_interval_ms: int | Omit = omit, max_wait_seconds: float | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: - """Wait for the vector store file to finish processing. - - `max_wait_seconds` must be finite and non-negative. When provided, it limits - polling and raises `TimeoutError` on expiry; zero prevents any polling requests. - Omit it to wait indefinitely. In-flight requests and retries use the client's - HTTP timeout settings and may finish after the polling deadline. Timing out - does not cancel processing on the server. - - Note: this will return even if the file failed to process, you need to check - file.last_error and file.status to handle these cases - """ - return _poll_vector_store_file( + "Attach a file to the given vector store and wait for it to be processed.\n\n `max_wait_seconds` limits polling after attachment; see `poll` for details.\n" + return _create_and_poll_vector_store_file( self, file_id, vector_store_id=vector_store_id, + attributes=attributes, poll_interval_ms=poll_interval_ms, max_wait_seconds=max_wait_seconds, + chunking_strategy=chunking_strategy, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, ) def upload( @@ -402,13 +416,13 @@ def upload( file: FileTypes, chunking_strategy: FileChunkingStrategyParam | Omit = omit, ) -> VectorStoreFile: - """Upload a file to the `files` API and then attach it to the given vector store. - - Note the file will be asynchronously processed (you can use the alternative - polling helper method to wait for processing to complete). - """ - file_obj = self._client.files.create(file=file, purpose="assistants") - return self.create(vector_store_id=vector_store_id, file_id=file_obj.id, chunking_strategy=chunking_strategy) + "Upload a file to the `files` API and then attach it to the given vector store.\n\n Note the file will be asynchronously processed (you can use the alternative\n polling helper method to wait for processing to complete).\n" + return _upload_vector_store_file( + self, + vector_store_id=vector_store_id, + file=file, + chunking_strategy=chunking_strategy, + ) def upload_and_poll( self, @@ -420,65 +434,32 @@ def upload_and_poll( max_wait_seconds: float | Omit = omit, chunking_strategy: FileChunkingStrategyParam | Omit = omit, ) -> VectorStoreFile: - """Add a file to a vector store and poll until processing is complete. - - `max_wait_seconds` limits polling after upload and attachment; see `poll` for details. - """ - _validate_max_wait_seconds(max_wait_seconds) - file_obj = self._client.files.create(file=file, purpose="assistants") - return self.create_and_poll( + "Add a file to a vector store and poll until processing is complete.\n\n `max_wait_seconds` limits polling after upload and attachment; see `poll` for details.\n" + return _upload_and_poll_vector_store_file( + self, vector_store_id=vector_store_id, - file_id=file_obj.id, - chunking_strategy=chunking_strategy, + file=file, + attributes=attributes, poll_interval_ms=poll_interval_ms, max_wait_seconds=max_wait_seconds, - attributes=attributes, + chunking_strategy=chunking_strategy, ) - def content( + def poll( self, file_id: str, *, vector_store_id: str, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx2.Timeout | None | NotGiven = not_given, - ) -> SyncPage[FileContentResponse]: - """ - Retrieve the parsed contents of a vector store file. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - if not file_id: - raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") - extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} - return self._get_api_list( - path_template( - "/vector_stores/{vector_store_id}/files/{file_id}/content", - vector_store_id=vector_store_id, - file_id=file_id, - ), - page=SyncPage[FileContentResponse], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - security={"bearer_auth": True}, - ), - model=FileContentResponse, + poll_interval_ms: int | Omit = omit, + max_wait_seconds: float | Omit = omit, + ) -> VectorStoreFile: + "Wait for the vector store file to finish processing.\n\n `max_wait_seconds` must be finite and non-negative. When provided, it limits\n polling and raises `TimeoutError` on expiry; zero prevents any polling requests.\n Omit it to wait indefinitely. In-flight requests and retries use the client's\n HTTP timeout settings and may finish after the polling deadline. Timing out\n does not cancel processing on the server.\n\n Note: this will return even if the file failed to process, you need to check\n file.last_error and file.status to handle these cases\n" + return _poll_vector_store_file( + self, + file_id, + vector_store_id=vector_store_id, + poll_interval_ms=poll_interval_ms, + max_wait_seconds=max_wait_seconds, ) @@ -781,70 +762,79 @@ async def delete( cast_to=VectorStoreFileDeleted, ) - async def create_and_poll( + def content( self, file_id: str, *, vector_store_id: str, - attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, - poll_interval_ms: int | Omit = omit, - max_wait_seconds: float | Omit = omit, - chunking_strategy: FileChunkingStrategyParam | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx2.Timeout | None | NotGiven = not_given, - ) -> VectorStoreFile: - """Attach a file to the given vector store and wait for it to be processed. - - `max_wait_seconds` limits polling after attachment; see `poll` for details. + ) -> AsyncPaginator[FileContentResponse, AsyncPage[FileContentResponse]]: """ - _validate_max_wait_seconds(max_wait_seconds) - await self.create( - vector_store_id=vector_store_id, - file_id=file_id, - chunking_strategy=chunking_strategy, - attributes=attributes, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) + Retrieve the parsed contents of a vector store file. - return await self.poll( - file_id, - vector_store_id=vector_store_id, - poll_interval_ms=poll_interval_ms, - max_wait_seconds=max_wait_seconds, + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not vector_store_id: + raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} + return self._get_api_list( + path_template( + "/vector_stores/{vector_store_id}/files/{file_id}/content", + vector_store_id=vector_store_id, + file_id=file_id, + ), + page=AsyncPage[FileContentResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + model=FileContentResponse, ) - async def poll( + async def create_and_poll( self, file_id: str, *, vector_store_id: str, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, poll_interval_ms: int | Omit = omit, max_wait_seconds: float | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: - """Wait for the vector store file to finish processing. - - `max_wait_seconds` must be finite and non-negative. When provided, it limits - polling and raises `TimeoutError` on expiry; zero prevents any polling requests. - Omit it to wait indefinitely. In-flight requests and retries use the client's - HTTP timeout settings and may finish after the polling deadline. Timing out - does not cancel processing on the server. - - Note: this will return even if the file failed to process, you need to check - file.last_error and file.status to handle these cases - """ - return await _async_poll_vector_store_file( + "Attach a file to the given vector store and wait for it to be processed.\n\n `max_wait_seconds` limits polling after attachment; see `poll` for details.\n" + return await _async_create_and_poll_vector_store_file( self, file_id, vector_store_id=vector_store_id, + attributes=attributes, poll_interval_ms=poll_interval_ms, max_wait_seconds=max_wait_seconds, + chunking_strategy=chunking_strategy, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, ) async def upload( @@ -854,14 +844,12 @@ async def upload( file: FileTypes, chunking_strategy: FileChunkingStrategyParam | Omit = omit, ) -> VectorStoreFile: - """Upload a file to the `files` API and then attach it to the given vector store. - - Note the file will be asynchronously processed (you can use the alternative - polling helper method to wait for processing to complete). - """ - file_obj = await self._client.files.create(file=file, purpose="assistants") - return await self.create( - vector_store_id=vector_store_id, file_id=file_obj.id, chunking_strategy=chunking_strategy + "Upload a file to the `files` API and then attach it to the given vector store.\n\n Note the file will be asynchronously processed (you can use the alternative\n polling helper method to wait for processing to complete).\n" + return await _async_upload_vector_store_file( + self, + vector_store_id=vector_store_id, + file=file, + chunking_strategy=chunking_strategy, ) async def upload_and_poll( @@ -874,65 +862,32 @@ async def upload_and_poll( max_wait_seconds: float | Omit = omit, chunking_strategy: FileChunkingStrategyParam | Omit = omit, ) -> VectorStoreFile: - """Add a file to a vector store and poll until processing is complete. - - `max_wait_seconds` limits polling after upload and attachment; see `poll` for details. - """ - _validate_max_wait_seconds(max_wait_seconds) - file_obj = await self._client.files.create(file=file, purpose="assistants") - return await self.create_and_poll( + "Add a file to a vector store and poll until processing is complete.\n\n `max_wait_seconds` limits polling after upload and attachment; see `poll` for details.\n" + return await _async_upload_and_poll_vector_store_file( + self, vector_store_id=vector_store_id, - file_id=file_obj.id, + file=file, + attributes=attributes, poll_interval_ms=poll_interval_ms, max_wait_seconds=max_wait_seconds, chunking_strategy=chunking_strategy, - attributes=attributes, ) - def content( + async def poll( self, file_id: str, *, vector_store_id: str, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx2.Timeout | None | NotGiven = not_given, - ) -> AsyncPaginator[FileContentResponse, AsyncPage[FileContentResponse]]: - """ - Retrieve the parsed contents of a vector store file. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - if not file_id: - raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") - extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} - return self._get_api_list( - path_template( - "/vector_stores/{vector_store_id}/files/{file_id}/content", - vector_store_id=vector_store_id, - file_id=file_id, - ), - page=AsyncPage[FileContentResponse], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - security={"bearer_auth": True}, - ), - model=FileContentResponse, + poll_interval_ms: int | Omit = omit, + max_wait_seconds: float | Omit = omit, + ) -> VectorStoreFile: + "Wait for the vector store file to finish processing.\n\n `max_wait_seconds` must be finite and non-negative. When provided, it limits\n polling and raises `TimeoutError` on expiry; zero prevents any polling requests.\n Omit it to wait indefinitely. In-flight requests and retries use the client's\n HTTP timeout settings and may finish after the polling deadline. Timing out\n does not cancel processing on the server.\n\n Note: this will return even if the file failed to process, you need to check\n file.last_error and file.status to handle these cases\n" + return await _async_poll_vector_store_file( + self, + file_id, + vector_store_id=vector_store_id, + poll_interval_ms=poll_interval_ms, + max_wait_seconds=max_wait_seconds, ) diff --git a/src/openai/resources/videos.py b/src/openai/resources/videos.py index d4c84ea1f3..9213e23e24 100644 --- a/src/openai/resources/videos.py +++ b/src/openai/resources/videos.py @@ -22,7 +22,7 @@ ) from .._files import deepcopy_with_paths from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given -from .._utils import extract_files, path_template, maybe_transform, async_maybe_transform +from .._utils import is_given, extract_files, path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -36,7 +36,6 @@ from ..pagination import SyncConversationCursorPage, AsyncConversationCursorPage from ..types.video import Video from .._base_client import AsyncPaginator, make_request_options -from .._utils._utils import is_given from ..types.video_size import VideoSize from ..types.video_seconds import VideoSeconds from ..types.video_model_param import VideoModelParam @@ -136,81 +135,6 @@ def create( cast_to=Video, ) - @typing_extensions.deprecated("The Sora API is scheduled to permanently shut down on September 24, 2026.") - def create_and_poll( - self, - *, - prompt: str, - input_reference: video_create_params.InputReference | Omit = omit, - model: VideoModelParam | Omit = omit, - seconds: VideoSeconds | Omit = omit, - size: VideoSize | Omit = omit, - poll_interval_ms: int | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx2.Timeout | None | NotGiven = not_given, - ) -> Video: - """Create a video and wait for it to be processed.""" - video = self.create( # pyright: ignore[reportDeprecated] - model=model, - prompt=prompt, - input_reference=input_reference, - seconds=seconds, - size=size, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - - return self.poll( # pyright: ignore[reportDeprecated] - video.id, - poll_interval_ms=poll_interval_ms, - ) - - @typing_extensions.deprecated("The Sora API is scheduled to permanently shut down on September 24, 2026.") - def poll( - self, - video_id: str, - *, - poll_interval_ms: int | Omit = omit, - ) -> Video: - """Wait for the vector store file to finish processing. - - Note: this will return even if the file failed to process, you need to check - file.last_error and file.status to handle these cases - """ - headers: dict[str, str] = {"X-Stainless-Poll-Helper": "true"} - if is_given(poll_interval_ms): - headers["X-Stainless-Custom-Poll-Interval"] = str(poll_interval_ms) - - while True: - response = self.with_raw_response.retrieve( # pyright: ignore[reportDeprecated] - video_id, - extra_headers=headers, - ) - - video = response.parse() - if video.status == "in_progress" or video.status == "queued": - if not is_given(poll_interval_ms): - from_header = response.headers.get("openai-poll-after-ms") - if from_header is not None: - poll_interval_ms = int(from_header) - else: - poll_interval_ms = 1000 - - self._sleep(poll_interval_ms / 1000) - elif video.status == "completed" or video.status == "failed": - return video - else: - if TYPE_CHECKING: # type: ignore[unreachable] - assert_never(video.status) - else: - return video - @typing_extensions.deprecated("The Sora API is scheduled to permanently shut down on September 24, 2026.") def retrieve( self, @@ -637,6 +561,75 @@ def remix( cast_to=Video, ) + @typing_extensions.deprecated("The Sora API is scheduled to permanently shut down on September 24, 2026.") + def create_and_poll( + self, + *, + prompt: str, + input_reference: video_create_params.InputReference | Omit = omit, + model: VideoModelParam | Omit = omit, + seconds: VideoSeconds | Omit = omit, + size: VideoSize | Omit = omit, + poll_interval_ms: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, + ) -> Video: + "Create a video and wait for it to be processed." + video = self.create( # pyright: ignore[reportDeprecated] + model=model, + prompt=prompt, + input_reference=input_reference, + seconds=seconds, + size=size, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + return self.poll( # pyright: ignore[reportDeprecated] + video.id, + poll_interval_ms=poll_interval_ms, + ) + + @typing_extensions.deprecated("The Sora API is scheduled to permanently shut down on September 24, 2026.") + def poll( + self, + video_id: str, + *, + poll_interval_ms: int | Omit = omit, + ) -> Video: + "Wait for the vector store file to finish processing.\n\n Note: this will return even if the file failed to process, you need to check\n file.last_error and file.status to handle these cases\n" + headers: dict[str, str] = {"X-Stainless-Poll-Helper": "true"} + if is_given(poll_interval_ms): + headers["X-Stainless-Custom-Poll-Interval"] = str(poll_interval_ms) + + while True: + response = self.with_raw_response.retrieve( # pyright: ignore[reportDeprecated] + video_id, + extra_headers=headers, + ) + + video = response.parse() + if video.status == "in_progress" or video.status == "queued": + if not is_given(poll_interval_ms): + from_header = response.headers.get("openai-poll-after-ms") + if from_header is not None: + poll_interval_ms = int(from_header) + else: + poll_interval_ms = 1000 + + self._sleep(poll_interval_ms / 1000) + elif video.status == "completed" or video.status == "failed": + return video + else: + if TYPE_CHECKING: # type: ignore[unreachable] + assert_never(video.status) + else: + return video + class AsyncVideos(AsyncAPIResource): @cached_property @@ -727,81 +720,6 @@ async def create( cast_to=Video, ) - @typing_extensions.deprecated("The Sora API is scheduled to permanently shut down on September 24, 2026.") - async def create_and_poll( - self, - *, - prompt: str, - input_reference: video_create_params.InputReference | Omit = omit, - model: VideoModelParam | Omit = omit, - seconds: VideoSeconds | Omit = omit, - size: VideoSize | Omit = omit, - poll_interval_ms: int | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx2.Timeout | None | NotGiven = not_given, - ) -> Video: - """Create a video and wait for it to be processed.""" - video = await self.create( # pyright: ignore[reportDeprecated] - model=model, - prompt=prompt, - input_reference=input_reference, - seconds=seconds, - size=size, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - - return await self.poll( # pyright: ignore[reportDeprecated] - video.id, - poll_interval_ms=poll_interval_ms, - ) - - @typing_extensions.deprecated("The Sora API is scheduled to permanently shut down on September 24, 2026.") - async def poll( - self, - video_id: str, - *, - poll_interval_ms: int | Omit = omit, - ) -> Video: - """Wait for the vector store file to finish processing. - - Note: this will return even if the file failed to process, you need to check - file.last_error and file.status to handle these cases - """ - headers: dict[str, str] = {"X-Stainless-Poll-Helper": "true"} - if is_given(poll_interval_ms): - headers["X-Stainless-Custom-Poll-Interval"] = str(poll_interval_ms) - - while True: - response = await self.with_raw_response.retrieve( # pyright: ignore[reportDeprecated] - video_id, - extra_headers=headers, - ) - - video = response.parse() - if video.status == "in_progress" or video.status == "queued": - if not is_given(poll_interval_ms): - from_header = response.headers.get("openai-poll-after-ms") - if from_header is not None: - poll_interval_ms = int(from_header) - else: - poll_interval_ms = 1000 - - await self._sleep(poll_interval_ms / 1000) - elif video.status == "completed" or video.status == "failed": - return video - else: - if TYPE_CHECKING: # type: ignore[unreachable] - assert_never(video.status) - else: - return video - @typing_extensions.deprecated("The Sora API is scheduled to permanently shut down on September 24, 2026.") async def retrieve( self, @@ -1230,6 +1148,75 @@ async def remix( cast_to=Video, ) + @typing_extensions.deprecated("The Sora API is scheduled to permanently shut down on September 24, 2026.") + async def create_and_poll( + self, + *, + prompt: str, + input_reference: video_create_params.InputReference | Omit = omit, + model: VideoModelParam | Omit = omit, + seconds: VideoSeconds | Omit = omit, + size: VideoSize | Omit = omit, + poll_interval_ms: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, + ) -> Video: + "Create a video and wait for it to be processed." + video = await self.create( # pyright: ignore[reportDeprecated] + model=model, + prompt=prompt, + input_reference=input_reference, + seconds=seconds, + size=size, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + return await self.poll( # pyright: ignore[reportDeprecated] + video.id, + poll_interval_ms=poll_interval_ms, + ) + + @typing_extensions.deprecated("The Sora API is scheduled to permanently shut down on September 24, 2026.") + async def poll( + self, + video_id: str, + *, + poll_interval_ms: int | Omit = omit, + ) -> Video: + "Wait for the vector store file to finish processing.\n\n Note: this will return even if the file failed to process, you need to check\n file.last_error and file.status to handle these cases\n" + headers: dict[str, str] = {"X-Stainless-Poll-Helper": "true"} + if is_given(poll_interval_ms): + headers["X-Stainless-Custom-Poll-Interval"] = str(poll_interval_ms) + + while True: + response = await self.with_raw_response.retrieve( # pyright: ignore[reportDeprecated] + video_id, + extra_headers=headers, + ) + + video = response.parse() + if video.status == "in_progress" or video.status == "queued": + if not is_given(poll_interval_ms): + from_header = response.headers.get("openai-poll-after-ms") + if from_header is not None: + poll_interval_ms = int(from_header) + else: + poll_interval_ms = 1000 + + await self._sleep(poll_interval_ms / 1000) + elif video.status == "completed" or video.status == "failed": + return video + else: + if TYPE_CHECKING: # type: ignore[unreachable] + assert_never(video.status) + else: + return video + class VideosWithRawResponse: def __init__(self, videos: Videos) -> None: