This release is a pre-release and may not be stable for production use.
pytest-stream-lifecycle
Test streaming delivery and disconnect cleanup over real loopback HTTP.
Experimental alpha. Python 3.11+, asyncio. No GPU, model API credentials, or external service is needed.
What it catches
A final response-body assertion can pass even when every token was buffered. Closing a client can also leave a producer or upstream task running. This plugin provides:
stream_server: an ASGI app served by Uvicorn on a retained local socket.server.sse()/server.lines(): bounded incremental reads over real HTTP/1.1.stream_probe: explicit resource observations and cleanup acknowledgements.
It is not a load tester, LLM evaluator, SSE parser, or general leak detector. The SSE parser is httpx-sse.
Install
Install the experimental alpha in your test environment:
python -m pip install pytest-stream-lifecycle==0.1.0a2
To install the current source instead:
python -m pip install 'git+https://github.com/shenxi200-eng/pytest-stream-lifecycle.git'
Maintainers can follow the release checklist.
For the example below, also install fastapi and sse-starlette. These are
example/development dependencies, not plugin runtime requirements. Installation
registers the pytest fixtures automatically; pytest-asyncio is not required.
Test delivery, then disconnect
Save as test_stream.py and run python -m pytest -q test_stream.py:
import asyncio
from fastapi import FastAPI
from sse_starlette.sse import EventSourceResponse
def test_disconnect(stream_server, stream_probe):
async def scenario():
app = FastAPI()
release = asyncio.Event()
async def tokens():
with stream_probe.track("producer") as observed:
try:
yield {"event": "token", "data": "first"}
await release.wait()
yield {"event": "token", "data": "second"}
finally:
# This synthetic producer owns no external resource.
# In real code, await cleanup BEFORE marking it closed.
observed.mark_closed()
@app.get("/events")
async def events():
# Listens for disconnects even while tokens() is waiting.
return EventSourceResponse(tokens())
async with stream_server(app) as server:
async with server.sse("/events") as stream:
assert (await stream.next()).data == "first"
stream_probe.assert_active("producer")
# Assert while the server is STILL RUNNING: its shutdown must
# not hide an application that ignores client disconnects.
await stream_probe.wait_closed("producer", timeout=2)
assert not release.is_set()
asyncio.run(scenario())
The first event must arrive before the release gate opens. Exiting the stream context disconnects the client; it does not assert server cleanup. The separate probe assertion checks that an observed producer exited and explicitly acknowledged cleanup without a non-cancellation error.
For a real upstream, instrument its lifetime separately. Call mark_closed()
only after the cleanup you intend to test has completed successfully. Leaving
track() without that acknowledgement fails wait_closed(), even if the task
has already exited. The marker is application-supplied evidence, not an
independent guarantee that a remote provider stopped work or billing.
Executable comparisons
git clone https://github.com/shenxi200-eng/pytest-stream-lifecycle.git
cd pytest-stream-lifecycle
python -m venv .venv
# Activate .venv using your shell's activation command.
python -m pip install -e '.[dev]'
python -m pytest examples/test_contracts.py -v
python -m pytest -q
The examples compare a buffered implementation that passes a body-only test
with a streaming contract that detects it. They also cover incremental delivery,
20 sequential sessions, and a detached upstream that remains alive after the
producer closes. Negative examples use pytest.raises: a passing test means
the defect was detected, not that the intentionally broken app is correct.
Boundaries
- Same-loop asyncio and loopback HTTP/1.1 only: not Trio, HTTP/2, TLS, WebSockets, or browser/proxy emulation.
- Resource observations are explicit. Unknown resources do not count as clean; uninstrumented tasks are outside the probe's coverage.
- Cleanup checks should run before server teardown. Server shutdown can itself cancel request tasks and otherwise hide a broken disconnect handler.
- An in-process server cannot forcibly kill code that blocks the event loop or continually suppresses cancellation. Such apps need process isolation.
- A local cleanup marker does not prove remote cancellation, GPU work cessation, or a provider's billing state.
See the API guide, design and related work, and contribution guide. Development uses AI-assisted implementation and review; generated code and claims must be checked rather than treated as verification evidence. AI-assisted contributions have the same testing and review requirements as other contributions.
Metadata
Release files for pytest-stream-lifecycle 0.1.0a2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pytest_stream_lifecycle-0.1.0a2.tar.gz | 35.5 kB | With provenance | Details for pytest_stream_lifecycle-0.1.0a2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pytest_stream_lifecycle-0.1.0a2-py3-none-any.whl | Python 3 | none | any | Details for pytest_stream_lifecycle-0.1.0a2-py3-none-any.whl |
Total release size: 50.5 kB
Release files / pytest_stream_lifecycle-0.1.0a2.tar.gz
| Download URL | pytest_stream_lifecycle-0.1.0a2.tar.gz |
|---|---|
| Size | 35.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5d4d961f9f5853489ecf32962bf2ff2c810fc79d19fd933a264233d6ac34bdff
|
|
BLAKE2b-256 checksum How to use checksums |
f38d10dba9593a64cb5f127d2e6869aa8e72c95d5ef25d440f0bd3d8c5466655
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logRelease files / pytest_stream_lifecycle-0.1.0a2-py3-none-any.whl
| Download URL | pytest_stream_lifecycle-0.1.0a2-py3-none-any.whl |
|---|---|
| Size | 14.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
522df4c02ea9178a1815d53025a47c9230fd22f0755fd7d6b2fba5c8f8b8d8d6
|
|
BLAKE2b-256 checksum How to use checksums |
8caffb0393ffb99a6471339f48ced09e9a12ba101861cd1ebfd654fb1e0559a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logSecurity for pytest-stream-lifecycle 0.1.0a2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for pytest_stream_lifecycle-0.1.0a2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.