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

+38 -38
View File
@@ -15,9 +15,9 @@ def _patch_base_db(conn):
yield conn
with patch(
"application.api.user.base.db_session", _yield
"docsgpt.api.user.base.db_session", _yield
), patch(
"application.api.user.base.db_readonly", _yield
"docsgpt.api.user.base.db_readonly", _yield
):
yield
@@ -27,7 +27,7 @@ class TestTimeRangeGenerators:
pass
def test_generate_minute_range(self):
from application.api.user.base import generate_minute_range
from docsgpt.api.user.base import generate_minute_range
start = datetime.datetime(2024, 1, 1, 10, 0, 0)
end = datetime.datetime(2024, 1, 1, 10, 5, 0)
@@ -40,7 +40,7 @@ class TestTimeRangeGenerators:
assert all(val == 0 for val in result.values())
def test_generate_hourly_range(self):
from application.api.user.base import generate_hourly_range
from docsgpt.api.user.base import generate_hourly_range
start = datetime.datetime(2024, 1, 1, 10, 0, 0)
end = datetime.datetime(2024, 1, 1, 15, 0, 0)
@@ -53,7 +53,7 @@ class TestTimeRangeGenerators:
assert all(val == 0 for val in result.values())
def test_generate_date_range(self):
from application.api.user.base import generate_date_range
from docsgpt.api.user.base import generate_date_range
start = datetime.date(2024, 1, 1)
end = datetime.date(2024, 1, 5)
@@ -66,7 +66,7 @@ class TestTimeRangeGenerators:
assert all(val == 0 for val in result.values())
def test_single_minute_range(self):
from application.api.user.base import generate_minute_range
from docsgpt.api.user.base import generate_minute_range
time = datetime.datetime(2024, 1, 1, 10, 30, 0)
result = generate_minute_range(time, time)
@@ -84,7 +84,7 @@ class TestResolveToolDetails:
pass
def test_empty_tool_ids_list(self, mock_mongo_db):
from application.api.user.base import resolve_tool_details
from docsgpt.api.user.base import resolve_tool_details
result = resolve_tool_details([])
@@ -95,9 +95,9 @@ class TestResolveToolDetails:
class TestGetVectorStore:
pass
@patch("application.api.user.base.VectorCreator.create_vectorstore")
@patch("docsgpt.api.user.base.VectorCreator.create_vectorstore")
def test_creates_vector_store(self, mock_create):
from application.api.user.base import get_vector_store
from docsgpt.api.user.base import get_vector_store
mock_store = Mock()
mock_create.return_value = mock_store
@@ -128,7 +128,7 @@ class TestHandleImageUpload:
return cls._image_bytes()
def test_returns_existing_url_when_no_file(self, flask_app):
from application.api.user.base import handle_image_upload
from docsgpt.api.user.base import handle_image_upload
with flask_app.test_request_context():
mock_request = Mock()
@@ -144,7 +144,7 @@ class TestHandleImageUpload:
assert error is None
def test_uploads_new_image(self, flask_app):
from application.api.user.base import handle_image_upload
from docsgpt.api.user.base import handle_image_upload
with flask_app.test_request_context():
mock_file = FileStorage(
@@ -166,7 +166,7 @@ class TestHandleImageUpload:
mock_storage.save_file.assert_called_once()
def test_accepts_multi_picture_jpeg(self, flask_app):
from application.api.user.base import handle_image_upload
from docsgpt.api.user.base import handle_image_upload
# Pillow reports multi-picture JPEGs (e.g. iPhone portrait photos) as
# MPO; they must still be accepted under a .jpg extension.
@@ -189,7 +189,7 @@ class TestHandleImageUpload:
mock_storage.save_file.assert_called_once()
def test_uploads_image_with_non_ascii_basename(self, flask_app):
from application.api.user.base import handle_image_upload
from docsgpt.api.user.base import handle_image_upload
with flask_app.test_request_context():
mock_file = FileStorage(
@@ -206,7 +206,7 @@ class TestHandleImageUpload:
mock_storage.save_file.assert_called_once()
def test_ignores_empty_filename(self, flask_app):
from application.api.user.base import handle_image_upload
from docsgpt.api.user.base import handle_image_upload
with flask_app.test_request_context():
mock_file = Mock()
@@ -225,7 +225,7 @@ class TestHandleImageUpload:
mock_storage.save_file.assert_not_called()
def test_handles_upload_error(self, flask_app):
from application.api.user.base import handle_image_upload
from docsgpt.api.user.base import handle_image_upload
with flask_app.app_context():
mock_file = FileStorage(
@@ -245,7 +245,7 @@ class TestHandleImageUpload:
assert error.status_code == 400
def test_rejects_non_image_content(self, flask_app):
from application.api.user.base import handle_image_upload
from docsgpt.api.user.base import handle_image_upload
with flask_app.app_context():
mock_file = FileStorage(
@@ -263,11 +263,11 @@ class TestHandleImageUpload:
mock_storage.save_file.assert_not_called()
def test_accepts_image_at_encoded_byte_limit(self, flask_app):
from application.api.user.base import handle_image_upload
from docsgpt.api.user.base import handle_image_upload
payload = self._png_bytes()
with patch(
"application.api.user.base.settings.AGENT_IMAGE_MAX_BYTES",
"docsgpt.api.user.base.settings.AGENT_IMAGE_MAX_BYTES",
len(payload),
), flask_app.app_context():
mock_file = FileStorage(
@@ -284,11 +284,11 @@ class TestHandleImageUpload:
mock_storage.save_file.assert_called_once()
def test_rejects_image_over_encoded_byte_limit(self, flask_app):
from application.api.user.base import handle_image_upload
from docsgpt.api.user.base import handle_image_upload
payload = self._png_bytes()
with patch(
"application.api.user.base.settings.AGENT_IMAGE_MAX_BYTES",
"docsgpt.api.user.base.settings.AGENT_IMAGE_MAX_BYTES",
len(payload) - 1,
), flask_app.app_context():
mock_file = FileStorage(
@@ -311,11 +311,11 @@ class TestHandleImageUpload:
def test_enforces_decoded_pixel_limit(
self, flask_app, width, height, expected_status
):
from application.api.user.base import handle_image_upload
from docsgpt.api.user.base import handle_image_upload
payload = self._image_bytes(width, height)
with patch(
"application.api.user.base.settings.AGENT_IMAGE_MAX_PIXELS", 16
"docsgpt.api.user.base.settings.AGENT_IMAGE_MAX_PIXELS", 16
), flask_app.app_context():
mock_file = FileStorage(
stream=io.BytesIO(payload), filename="dimensions.png"
@@ -338,7 +338,7 @@ class TestHandleImageUpload:
def test_rejects_image_whose_content_does_not_match_extension(
self, flask_app
):
from application.api.user.base import handle_image_upload
from docsgpt.api.user.base import handle_image_upload
with flask_app.app_context():
mock_file = FileStorage(
@@ -355,7 +355,7 @@ class TestHandleImageUpload:
mock_storage.save_file.assert_not_called()
def test_sanitizes_user_directory_component(self, flask_app):
from application.api.user.base import handle_image_upload
from docsgpt.api.user.base import handle_image_upload
with flask_app.app_context():
mock_file = FileStorage(
@@ -379,7 +379,7 @@ class TestRequireAgentDecorator:
pass
def test_returns_400_for_missing_token(self, flask_app):
from application.api.user.base import require_agent
from docsgpt.api.user.base import require_agent
with flask_app.app_context():
@@ -400,7 +400,7 @@ class TestRequireAgentDecorator:
class TestEnsureUserDocPgConn:
def test_creates_new_user_doc(self, pg_conn):
from application.api.user.base import ensure_user_doc
from docsgpt.api.user.base import ensure_user_doc
with _patch_base_db(pg_conn):
doc = ensure_user_doc("brand-new-user")
@@ -410,8 +410,8 @@ class TestEnsureUserDocPgConn:
assert prefs.get("shared_with_me") == []
def test_preserves_existing_prefs(self, pg_conn):
from application.api.user.base import ensure_user_doc
from application.storage.db.repositories.users import UsersRepository
from docsgpt.api.user.base import ensure_user_doc
from docsgpt.storage.db.repositories.users import UsersRepository
user = "existing-user"
UsersRepository(pg_conn).upsert(user)
@@ -425,18 +425,18 @@ class TestEnsureUserDocPgConn:
class TestResolveToolDetailsPgConn:
def test_empty_list_returns_empty(self, pg_conn):
from application.api.user.base import resolve_tool_details
from docsgpt.api.user.base import resolve_tool_details
with _patch_base_db(pg_conn):
assert resolve_tool_details([]) == []
def test_none_entries_filtered_out(self, pg_conn):
from application.api.user.base import resolve_tool_details
from docsgpt.api.user.base import resolve_tool_details
with _patch_base_db(pg_conn):
assert resolve_tool_details([None, ""]) == []
def test_resolves_known_uuid_ids(self, pg_conn):
from application.api.user.base import resolve_tool_details
from application.storage.db.repositories.user_tools import (
from docsgpt.api.user.base import resolve_tool_details
from docsgpt.storage.db.repositories.user_tools import (
UserToolsRepository,
)
@@ -452,7 +452,7 @@ class TestResolveToolDetailsPgConn:
assert got[0]["display_name"] == "Custom"
def test_unknown_ids_skipped(self, pg_conn):
from application.api.user.base import resolve_tool_details
from docsgpt.api.user.base import resolve_tool_details
with _patch_base_db(pg_conn):
got = resolve_tool_details(
["00000000-0000-0000-0000-000000000000"]
@@ -460,8 +460,8 @@ class TestResolveToolDetailsPgConn:
assert got == []
def test_legacy_ids_lookup(self, pg_conn):
from application.api.user.base import resolve_tool_details
from application.storage.db.repositories.user_tools import (
from docsgpt.api.user.base import resolve_tool_details
from docsgpt.storage.db.repositories.user_tools import (
UserToolsRepository,
)
@@ -479,7 +479,7 @@ class TestResolveToolDetailsPgConn:
class TestRequireAgentPgConn:
def test_returns_404_invalid_token(self, pg_conn, flask_app):
from application.api.user.base import require_agent
from docsgpt.api.user.base import require_agent
@require_agent
def fn(webhook_token=None, agent=None, agent_id_str=None):
@@ -490,8 +490,8 @@ class TestRequireAgentPgConn:
assert result.status_code == 404
def test_injects_agent_when_valid(self, pg_conn, flask_app):
from application.api.user.base import require_agent
from application.storage.db.repositories.agents import AgentsRepository
from docsgpt.api.user.base import require_agent
from docsgpt.storage.db.repositories.agents import AgentsRepository
agent = AgentsRepository(pg_conn).create(
"owner", "wh-agent", "published",