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.
This commit is contained in:
Alex committed 2026-09-07 10:20:43 +01:00
1 parent 3e7f1f91b4
commit 574f96341e
984 files changed
+9847 -9682

No files matched your search

+50 -50
View File
@@ -54,7 +54,7 @@ from unittest.mock import MagicMock, patch
# ---------------------------------------------------------------------------
# Bootstrap: import application/* and connect to the configured Postgres.
# Bootstrap: import docsgpt/* and connect to the configured Postgres.
# ---------------------------------------------------------------------------
ROOT = Path(__file__).resolve().parent.parent.parent
@@ -84,9 +84,9 @@ UNIQUE = f"qa_e2e_{int(time.time())}_{uuid.uuid4().hex[:6]}"
# Quiet noisy library loggers so the script's own output stays readable.
for name in (
"application", "application.api", "application.storage",
"application.usage", "application.parser",
"application.api.user.reconciliation",
"application", "docsgpt.api", "docsgpt.storage",
"docsgpt.usage", "docsgpt.parser",
"docsgpt.api.user.reconciliation",
):
logging.getLogger(name).setLevel(logging.ERROR)
@@ -260,11 +260,11 @@ def s1_wal_and_reconciler() -> None:
a stuck pending row to ``failed`` after 3 attempts and emits a
structured alert."""
from application.api.answer.services.conversation_service import (
from docsgpt.api.answer.services.conversation_service import (
ConversationService,
TERMINATED_RESPONSE_PLACEHOLDER,
)
from application.api.user.reconciliation import run_reconciliation
from docsgpt.api.user.reconciliation import run_reconciliation
user = f"{UNIQUE}-s1"
with ENGINE.begin() as c:
@@ -273,7 +273,7 @@ def s1_wal_and_reconciler() -> None:
{"u": user},
)
with patch_db_session("application.api.answer.services.conversation_service"):
with patch_db_session("docsgpt.api.answer.services.conversation_service"):
res = ConversationService().save_user_question(
conversation_id=None,
question=f"{UNIQUE} s1 stuck WAL",
@@ -348,8 +348,8 @@ def s2_webhook_idempotency_and_scoping() -> None:
from flask import Flask
from application.api.user.agents.webhooks import AgentWebhookListener
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.agents.webhooks import AgentWebhookListener
from docsgpt.storage.db.repositories.agents import AgentsRepository
user_a = f"{UNIQUE}-s2a"
user_b = f"{UNIQUE}-s2b"
@@ -382,8 +382,8 @@ def s2_webhook_idempotency_and_scoping() -> None:
raw_key = f"{UNIQUE}-key-1"
# Sequential dedup: same key twice → same task_id, single apply_async call.
with patch_db_session("application.api.user.agents.webhooks"), patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
with patch_db_session("docsgpt.api.user.agents.webhooks"), patch(
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
apply_mock,
):
for _ in range(2):
@@ -410,8 +410,8 @@ def s2_webhook_idempotency_and_scoping() -> None:
apply_mock.reset_mock()
apply_calls.clear()
cross_key = f"{UNIQUE}-cross"
with patch_db_session("application.api.user.agents.webhooks"), patch(
"application.api.user.agents.webhooks.process_agent_webhook.apply_async",
with patch_db_session("docsgpt.api.user.agents.webhooks"), patch(
"docsgpt.api.user.agents.webhooks.process_agent_webhook.apply_async",
apply_mock,
):
for ag in (agent_a, agent_b):
@@ -453,11 +453,11 @@ def s3_ingest_deterministic_and_resume() -> None:
"""Deterministic source_id is stable; chunk-progress checkpoint
resumes from the next un-embedded chunk."""
from application.parser.embedding_pipeline import _read_resume_index
from application.storage.db.repositories.ingest_chunk_progress import (
from docsgpt.parser.embedding_pipeline import _read_resume_index
from docsgpt.storage.db.repositories.ingest_chunk_progress import (
IngestChunkProgressRepository,
)
from application.worker import _derive_source_id
from docsgpt.worker import _derive_source_id
# Same scoped key → same uuid5; different scope → different uuid5.
a = _derive_source_id("alice:UPLOAD-1")
@@ -475,7 +475,7 @@ def s3_ingest_deterministic_and_resume() -> None:
# Checkpoint primitive: seed progress, observe resume index.
src_id = uuid.uuid4()
with patch_db_session("application.parser.embedding_pipeline"):
with patch_db_session("docsgpt.parser.embedding_pipeline"):
with ENGINE.begin() as conn:
repo = IngestChunkProgressRepository(conn)
repo.init_progress(str(src_id), total_chunks=10)
@@ -497,11 +497,11 @@ def s4_token_usage_atomic_rollback() -> None:
insert; the surrounding transaction must roll back so no orphan
token_usage row survives."""
from application.api.answer.services.conversation_service import (
from docsgpt.api.answer.services.conversation_service import (
ConversationService,
)
from application.storage.db.repositories import conversations as conv_mod
from application.storage.db.repositories.conversations import (
from docsgpt.storage.db.repositories import conversations as conv_mod
from docsgpt.storage.db.repositories.conversations import (
ConversationsRepository,
)
@@ -531,8 +531,8 @@ def s4_token_usage_atomic_rollback() -> None:
try:
conv_mod.ConversationsRepository.update_message_by_id = explode
with patch_db_session(
"application.api.answer.services.conversation_service",
), suppress_logging("application.api.answer.services.conversation_service"):
"docsgpt.api.answer.services.conversation_service",
), suppress_logging("docsgpt.api.answer.services.conversation_service"):
try:
ConversationService().finalize_message(
mid,
@@ -577,7 +577,7 @@ def s5_reconciler_tool_call_sweeps() -> None:
flips proposed→failed and executed→failed (or compensated when the
tool exposes a working compensate)."""
from application.api.user.reconciliation import run_reconciliation
from docsgpt.api.user.reconciliation import run_reconciliation
user = f"{UNIQUE}-s5"
with ENGINE.begin() as c:
@@ -687,7 +687,7 @@ def s6_resume_janitor() -> None:
)
info(f"seeded pending_tool_state in 'resuming' (resumed_at=-11m) for conv={cid}")
from application.api.user.tasks import cleanup_pending_tool_state
from docsgpt.api.user.tasks import cleanup_pending_tool_state
res = cleanup_pending_tool_state.run()
info(f"janitor result: {res}")
@@ -712,13 +712,13 @@ def s7_token_usage_attribution() -> None:
``_persist_token_usage_inline`` self-write a row; the canary fires
when ``status='complete'`` arrives with zero counts."""
from application.api.answer.services.conversation_service import (
from docsgpt.api.answer.services.conversation_service import (
ConversationService,
)
from application.storage.db.repositories.conversations import (
from docsgpt.storage.db.repositories.conversations import (
ConversationsRepository,
)
from application.usage import _maybe_persist_inline
from docsgpt.usage import _maybe_persist_inline
user = f"{UNIQUE}-s7"
with ENGINE.begin() as c:
@@ -747,7 +747,7 @@ def s7_token_usage_attribution() -> None:
mid_zero = str(msg2["id"])
# 1. Real counts → row written, source='agent_stream'.
with patch_db_session("application.api.answer.services.conversation_service"):
with patch_db_session("docsgpt.api.answer.services.conversation_service"):
ok = ConversationService().finalize_message(
mid_real,
"answer",
@@ -771,7 +771,7 @@ def s7_token_usage_attribution() -> None:
# 2. Zero-count complete fires the canary alert.
canary_logger = logging.getLogger(
"application.api.answer.services.conversation_service"
"docsgpt.api.answer.services.conversation_service"
)
seen: list[logging.LogRecord] = []
@@ -787,7 +787,7 @@ def s7_token_usage_attribution() -> None:
canary_logger.setLevel(logging.WARNING)
try:
with patch_db_session(
"application.api.answer.services.conversation_service",
"docsgpt.api.answer.services.conversation_service",
):
ConversationService().finalize_message(
mid_zero,
@@ -817,7 +817,7 @@ def s7_token_usage_attribution() -> None:
_persist_token_usage_inline = True
_token_usage_source = "compression"
with patch_db_session("application.usage"):
with patch_db_session("docsgpt.usage"):
_maybe_persist_inline(
_SideLLM(),
{"prompt_tokens": 250, "generated_tokens": 80},
@@ -839,10 +839,10 @@ def s8_regenerate_replaces() -> None:
position N before reserving the placeholder, so the new row lands at
position=N (replacing the old) rather than appending at the end."""
from application.api.answer.services.conversation_service import (
from docsgpt.api.answer.services.conversation_service import (
ConversationService,
)
from application.storage.db.repositories.conversations import (
from docsgpt.storage.db.repositories.conversations import (
ConversationsRepository,
)
@@ -871,7 +871,7 @@ def s8_regenerate_replaces() -> None:
assert before == [0, 1, 2, 3, 4], f"seed wrong: {before}"
info(f"seeded conv={cid} positions={before}")
with patch_db_session("application.api.answer.services.conversation_service"):
with patch_db_session("docsgpt.api.answer.services.conversation_service"):
result = ConversationService().save_user_question(
conversation_id=cid,
question=f"{UNIQUE}-regen",
@@ -899,7 +899,7 @@ def s9_idempotency_concurrent_claim() -> None:
Confirms the ON CONFLICT DO NOTHING race semantics for the HTTP
claim-first pattern."""
from application.storage.db.repositories.idempotency import (
from docsgpt.storage.db.repositories.idempotency import (
IdempotencyRepository,
)
@@ -1033,7 +1033,7 @@ def _build_openai_llm():
"""Construct an OpenAI-provider LLM pointing at the mock LLM. Skips
the BYOM resolution path that LLMCreator usually goes through, since
we just want a direct openai-client wrapper for the mock."""
from application.llm.openai import OpenAILLM
from docsgpt.llm.openai import OpenAILLM
return OpenAILLM(
api_key="mock-key",
@@ -1061,7 +1061,7 @@ def s11_live_llm_stream_through_wal() -> None:
streamed response captured.
"""
from application.api.answer.services.conversation_service import (
from docsgpt.api.answer.services.conversation_service import (
ConversationService,
)
@@ -1072,7 +1072,7 @@ def s11_live_llm_stream_through_wal() -> None:
{"u": user},
)
with patch_db_session("application.api.answer.services.conversation_service"):
with patch_db_session("docsgpt.api.answer.services.conversation_service"):
res = ConversationService().save_user_question(
conversation_id=None,
question=f"{UNIQUE} say hello",
@@ -1105,7 +1105,7 @@ def s11_live_llm_stream_through_wal() -> None:
f"response[:40]={response_text[:40]!r}"
)
with patch_db_session("application.api.answer.services.conversation_service"):
with patch_db_session("docsgpt.api.answer.services.conversation_service"):
ok = ConversationService().finalize_message(
mid,
response_text,
@@ -1162,7 +1162,7 @@ def s12_full_embedding_loop() -> None:
is just a regular Python function; ``self`` is a MagicMock with the
minimum attrs the worker reads."""
from application.core.settings import settings
from docsgpt.core.settings import settings
user = f"{UNIQUE}-s12"
with ENGINE.begin() as c:
@@ -1227,7 +1227,7 @@ def s12_full_embedding_loop() -> None:
settings.VECTOR_STORE = "faiss"
idempotency_key = f"{user}:{UNIQUE}-s12-key"
from application.worker import _derive_source_id
from docsgpt.worker import _derive_source_id
expected_source_id = str(_derive_source_id(idempotency_key))
@@ -1250,11 +1250,11 @@ def s12_full_embedding_loop() -> None:
try:
with patch_db_session(
"application.parser.embedding_pipeline",
"application.worker",
"application.api.answer.services.conversation_service",
), patch("application.worker.upload_index", _fake_upload_index):
from application.worker import ingest_worker
"docsgpt.parser.embedding_pipeline",
"docsgpt.worker",
"docsgpt.api.answer.services.conversation_service",
), patch("docsgpt.worker.upload_index", _fake_upload_index):
from docsgpt.worker import ingest_worker
resp = ingest_worker(
fake_self,
@@ -1347,7 +1347,7 @@ class CeleryWorkerService:
self._proc = subprocess.Popen( # noqa: S603
[
sys.executable, "-m", "celery",
"-A", "application.app.celery", "worker",
"-A", "docsgpt.app.celery", "worker",
"-l", "INFO", "--pool=solo",
"-Q", self._queue,
"-n", f"qa-{UNIQUE}@%h",
@@ -1508,7 +1508,7 @@ def s13_sigkill_redelivers_via_acks_late() -> None:
# Dispatch via celery's send_task using the same broker URL the worker
# listens on. The DocsGPT celery instance has already loaded
# ``application.celeryconfig`` (broker = production .env DB 0); since
# ``docsgpt.celeryconfig`` (broker = production .env DB 0); since
# Celery's conf re-reads ``CELERY_BROKER_URL`` from the env at publish
# time, we override the env for the *whole* dispatch + wait block.
from celery import Celery
@@ -1536,7 +1536,7 @@ def s13_sigkill_redelivers_via_acks_late() -> None:
task_id = str(uuid.uuid4())
info(f"dispatching task_id={task_id} key={idempotency_key}")
chaos_app.send_task(
"application.api.user.tasks.process_agent_webhook",
"docsgpt.api.user.tasks.process_agent_webhook",
kwargs={
"agent_id": agent_id,
"payload": {"event": f"{UNIQUE}-s13"},
@@ -1636,7 +1636,7 @@ def s10_celery_default_queue() -> None:
"""Sanity: the broker queue is project-scoped so a sibling worker on
the same Redis can't grab DocsGPT tasks."""
from application import celeryconfig
from docsgpt import celeryconfig
assert celeryconfig.task_default_queue == "docsgpt", (
f"expected default queue 'docsgpt'; got {celeryconfig.task_default_queue!r}"