Skip to main content
Pre-release

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)

Source distribution for pytest-stream-lifecycle 0.1.0a2
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)

Table of built distributions (wheels) for pytest-stream-lifecycle 0.1.0a2
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page