Skip to content

fix: add aclose() to AsyncStream for standard async cleanup - #2854

Merged
marcuswood-oai merged 6 commits into
openai:mainfrom
landim:fix/async-stream-aclose
Sep 10, 2026
Merged

fix: add aclose() to AsyncStream for standard async cleanup#2854
marcuswood-oai merged 6 commits into
openai:mainfrom
landim:fix/async-stream-aclose

Conversation

@landim

@landim landim commented Feb 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Add AsyncStream.aclose() as a thin async alias for the existing close() method. This supports contextlib.aclosing() and adapters that expect aclose() while preserving existing close() and async context manager behavior, including subclass overrides of close().

Fixes #2853.

The reported cleanup error occurs when an instrumentation wrapper exposes an AsyncStream as its response and the higher-level chat streaming helper calls aclose() on it. The alias makes that cleanup path work without changing stream iteration or response handling.

Tests

  • Verify that aclose() closes an actual streaming HTTP response and remains safe alongside repeated close() calls.
  • Exercise contextlib.aclosing() through the public async client after an early exit and an exception.
  • Merge current main and adapt the original test to HTTPX2.

Validation

  • 23 streaming tests pass on Python 3.10 with both Pydantic v1 and v2.
  • 16 additional local compatibility checks pass with both Pydantic versions, covering existing cleanup methods, unread/partial/complete streams, subclass overrides, and the reported instrumentation cleanup path.
  • Broad local run: 5,108 passed, 120 skipped; three environment-related failures passed on rerun after installing local wheel metadata and using CI-pinned uv 0.12.5 (5,111 passing tests total).
  • Ruff, Pyright, Mypy, import checks, and changed-file formatting pass.
  • Wheel and source distribution builds, Python metadata validation, Bedrock wheel smoke checks, and isolated HTTPX2/legacy HTTPX/aiohttp wheel checks pass.

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.

`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.
@landim
landim requested a review from a team as a code owner February 10, 2026 15:53
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>
@vizovitin

Copy link
Copy Markdown

Are there any plans to actually include this into a release?

@landim

landim commented Mar 17, 2026

Copy link
Copy Markdown
Contributor Author

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-10T16:25:30.093404Z d6c3773 New commits
🔒 Security Review Completed 2026-09-10T16:25:47.462557Z d6c3773 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@marcuswood-oai

marcuswood-oai commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Thanks for the contribution, and sorry we left this open for so long!

Posted with Codex.

@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Castiron custom code

✅ No new custom-code files detected.

36 mixed files remain; 0 existing customizations changed.

Compared adb212e11632d6c3773c49ef. Generated baselines verified.

36 existing customizations unchanged
  • api.md
  • scripts/castiron/README.md
  • scripts/castiron/custom_code_report.py
  • scripts/castiron/test_custom_code_report.py
  • src/openai/init.py
  • src/openai/_client.py
  • src/openai/resources/audio/transcriptions.py
  • src/openai/resources/audio/translations.py
  • src/openai/resources/beta/beta.py
  • src/openai/resources/beta/responses/responses.py
  • src/openai/resources/beta/threads/runs/runs.py
  • src/openai/resources/beta/threads/threads.py
  • src/openai/resources/chat/completions/completions.py
  • src/openai/resources/embeddings.py
  • src/openai/resources/files.py
  • src/openai/resources/realtime/realtime.py
  • src/openai/resources/responses/responses.py
  • src/openai/resources/uploads/uploads.py
  • src/openai/resources/vector_stores/file_batches.py
  • src/openai/resources/vector_stores/files.py
  • src/openai/resources/videos.py
  • src/openai/resources/webhooks/init.py
  • src/openai/resources/webhooks/webhooks.py
  • src/openai/types/chat/init.py
  • src/openai/types/chat/chat_completion_message_tool_call.py
  • src/openai/types/fine_tuning/fine_tuning_job_integration.py
  • src/openai/types/responses/init.py
  • src/openai/types/responses/response.py
  • src/openai/types/responses/response_function_web_search.py
  • src/openai/types/responses/response_function_web_search_param.py
  • src/openai/types/responses/responses_client_event.py
  • src/openai/types/responses/responses_client_event_param.py
  • src/openai/types/responses/tool.py
  • src/openai/types/responses/tool_param.py
  • src/openai/types/webhooks/init.py
  • tests/api_resources/test_videos.py

A changed generated baseline means this report cannot reliably identify which handwritten lines changed.

Inspect the custom-code diff

Download 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.patch

Or 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.patch

This is the current full custom patch for mixed files, not an attribution of only the handwritten lines changed by this PR.

Full report and patch

@marcuswood-oai
marcuswood-oai merged commit 802b334 into openai:main Sep 10, 2026
18 checks passed
@openai-sdks openai-sdks Bot mentioned this pull request Sep 10, 2026
pull Bot pushed a commit to Pyrex77/openai-python that referenced this pull request Sep 10, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AsyncStream missing aclose() causes AttributeError in streaming structured output

3 participants