fix: add aclose() to AsyncStream for standard async cleanup - #2854
Conversation
`AsyncStream` exposes `close()` but not `aclose()`, which is the standard Python async cleanup method name (used by contextlib, asyncio, and the language spec for async generators). This causes `AttributeError` when callers use the conventional `aclose()` pattern. Two concrete callers in this repo are affected: - `AsyncChatCompletionStream.close()` stores `raw_stream.response` in `self._response` and calls `self._response.aclose()`. When instrumentation libraries (e.g. Langfuse) wrap the raw stream, the `.response` attribute can resolve to the `AsyncStream` itself rather than the underlying `httpx.Response`, hitting the missing method. - Third-party instrumentation (Langfuse `LangfuseResponseGeneratorAsync`) calls `.aclose()` on the response generator which delegates to the wrapped `AsyncStream`. The fix adds `aclose()` as a thin async alias for `close()`, matching the pattern already used by `httpx.Response`, `asyncio.StreamWriter`, and Python async generators.
Replace the smoke test with a mock-based assertion that aclose() actually calls close(), validating the behavioral contract. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Are there any plans to actually include this into a release? |
|
Hey @RobertCraigie, friendly bump on this. The issue is affecting Langfuse users in production (instrumentation wrapping AsyncStream triggers an AttributeError on aclose()). Happy to adjust anything if needed. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Thanks for the contribution, and sorry we left this open for so long! Posted with Codex. |
Castiron custom code✅ No new custom-code files detected. 36 mixed files remain; 0 existing customizations changed. Compared 36 existing customizations unchanged
A changed generated baseline means this report cannot reliably identify which handwritten lines changed. Inspect the custom-code diffDownload the exact patch produced by this run (requires repository access): gh run download 34503198685 --repo openai/openai-python \
--name castiron-custom-code-34503198685-1 --dir /tmp/castiron-custom-code-34503198685-1
git apply --stat /tmp/castiron-custom-code-34503198685-1/custom-code.patch
cat /tmp/castiron-custom-code-34503198685-1/custom-code.patchOr reproduce it from an SDK checkout containing the vendored reporter: git fetch --no-tags origin adb212e116323fcec4b4811d20ba9eb78280c24c d6c3773c49ef9d2696ce4ab2e6914c9949ae48b8
python3 scripts/castiron/custom_code_report.py report \
--base adb212e116323fcec4b4811d20ba9eb78280c24c \
--head d6c3773c49ef9d2696ce4ab2e6914c9949ae48b8 --fetch --require-head-hash --public \
--out /tmp/castiron-custom-code-d6c3773c49ef
cat /tmp/castiron-custom-code-d6c3773c49ef/custom-code.patchThis is the current full custom patch for mixed files, not an attribution of only the handwritten lines changed by this PR. |
Automated Release PR --- ## [3.12.0](openai/openai-python@v3.11.0...v3.12.0) (2026-09-10) ### Features * **api:** Add Live API ([0e4bfef](openai@0e4bfef)) ### Bug Fixes * add aclose() to AsyncStream for standard async cleanup ([openai#2854](openai#2854)) ([802b334](openai@802b334)) * handle bare `dict` and `list` annotations without type arguments ([openai#3760](openai#3760)) ([c7e8c03](openai@c7e8c03)) * preserve finalized output on null response completion ([openai#3345](openai#3345)) ([adb212e](openai@adb212e)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: openai-sdks[bot] <284451331+openai-sdks[bot]@users.noreply.github.com>
Summary
Add
AsyncStream.aclose()as a thin async alias for the existingclose()method. This supportscontextlib.aclosing()and adapters that expectaclose()while preserving existingclose()and async context manager behavior, including subclass overrides ofclose().Fixes #2853.
The reported cleanup error occurs when an instrumentation wrapper exposes an
AsyncStreamas its response and the higher-level chat streaming helper callsaclose()on it. The alias makes that cleanup path work without changing stream iteration or response handling.Tests
aclose()closes an actual streaming HTTP response and remains safe alongside repeatedclose()calls.contextlib.aclosing()through the public async client after an early exit and an exception.Validation
Local limitation: the pinned Steady mock server could not download its JSR dependencies in this environment. Forty fine-tuning tests failed because the server was unavailable, and API-resource tests were not run locally. CI still needs to validate the full mock-server suite and supported Python matrix.