Files
DocsGPT/tests/integration/_drain_harness_app.py
T
Alex 574f96341e refactor: rename the application package to docsgpt
The backend import package is now docsgpt, the name it will carry on PyPI;
application was far too generic to install into anyone's site-packages.
git mv plus a mechanical rewrite of every import, dotted string and path
reference: 734 Python files, the compose files, Dockerfile, workflows, docs,
setup scripts, devcontainer, k8s manifests, vscode config, pytest and coverage
config, .gitignore. Behaviour is unchanged.

Kept for one release:
- A top-level application package whose meta-path finder resolves
  application.x.y to the already-imported docsgpt.x.y object, so old imports
  and entry points (celery -A application.app.celery,
  uvicorn application.asgi:asgi_app) keep working with a FutureWarning.
- Celery registers every application.* task name as an alias of its
  docsgpt.* task on start-up, so messages queued by the previous release still
  run. The redbeat key prefix moves to redbeat:docsgpt:v2: so schedule entries
  the previous release wrote are left unread instead of firing twice.

The backend image builds from the repository root (docker build -f
docsgpt/Dockerfile .) so it can ship the alias package; a root .dockerignore
allow-lists docsgpt/ and application/ and keeps caches, local data, .env
files, the sample index files and the Dockerfile out. Compose and the image
workflows point at the new context.
2026-09-07 10:20:43 +01:00

52 lines
1.6 KiB
Python

"""Minimal ASGI app for the worker graceful-shutdown drain e2e.
Mirrors production (a Flask SSE generator behind a2wsgi's thread pool) without
Postgres/Redis. ``/sse`` holds the connection open like an idle subscriber;
``DRAIN_HARNESS_COOPERATIVE=1`` makes it poll the real
``docsgpt.core.shutdown.is_shutting_down`` flag (the fix), else it
reproduces the pre-fix hang.
"""
from __future__ import annotations
import os
import time
from a2wsgi import WSGIMiddleware
from flask import Flask, Response
from starlette.applications import Starlette
from starlette.routing import Mount
from docsgpt.core.shutdown import is_shutting_down
_COOPERATIVE = os.environ.get("DRAIN_HARNESS_COOPERATIVE") == "1"
_POLL_SECONDS = 1.0
_MAX_HOLD_SECONDS = 120.0
flask_app = Flask(__name__)
@flask_app.get("/health")
def health() -> Response:
return Response('{"ok": true}', mimetype="application/json")
@flask_app.get("/sse")
def sse() -> Response:
def generate():
# Emit headers immediately (like the real ": connected" frame) so the
# client establishes the stream, then hold without writing further.
yield ": connected\n\n"
deadline = time.monotonic() + _MAX_HOLD_SECONDS
while time.monotonic() < deadline:
# Cooperative variant bails on the flag; non-cooperative pins the
# a2wsgi thread to the deadline (the pre-fix hang).
if _COOPERATIVE and is_shutting_down():
break
time.sleep(_POLL_SECONDS)
return Response(generate(), mimetype="text/event-stream")
asgi_app = Starlette(routes=[Mount("/", app=WSGIMiddleware(flask_app, workers=8))])