mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-18 16:13:19 +00:00
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:
1 parent
3e7f1f91b4
commit
574f96341e
984 files changed
+9847
-9682
No files matched your search
@@ -0,0 +1,93 @@
|
||||
"""JSON-safe coercion for PG-native Python types.
|
||||
|
||||
Postgres (via psycopg) returns native Python types — ``uuid.UUID``,
|
||||
``datetime.datetime``/``datetime.date``, ``decimal.Decimal``, ``bytes``
|
||||
— that ``json.dumps`` rejects. This module is the single place those
|
||||
coercion rules live; everywhere else should call into it.
|
||||
|
||||
Two interfaces with identical coverage:
|
||||
|
||||
* :func:`coerce_pg_native` — recursive walk returning a JSON-safe copy.
|
||||
Use when you need to inspect the dict yourself or pass it to a
|
||||
serializer that doesn't accept a custom encoder (e.g. SQLAlchemy
|
||||
parameter binding for a JSONB column).
|
||||
* :class:`PGNativeJSONEncoder` — ``JSONEncoder`` subclass. Use as
|
||||
``json.dumps(obj, cls=PGNativeJSONEncoder)`` for serialise-once flows
|
||||
where the extra recursive walk is wasted work.
|
||||
|
||||
Coercion rules:
|
||||
|
||||
* ``UUID`` → canonical hex string.
|
||||
* ``datetime`` / ``date`` → ISO 8601 string.
|
||||
* ``Decimal`` → numeric string (preserves precision; ``float()`` would not).
|
||||
* ``bytes`` → base64 string. Lossless and universally JSON-safe;
|
||||
prior code used UTF-8 with ``errors="replace"`` which silently
|
||||
corrupted binary payloads (e.g. Gemini's ``thought_signature``).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
|
||||
def _coerce_scalar(obj: Any) -> Any:
|
||||
if isinstance(obj, UUID):
|
||||
return str(obj)
|
||||
if isinstance(obj, (datetime, date)):
|
||||
return obj.isoformat()
|
||||
if isinstance(obj, Decimal):
|
||||
return str(obj)
|
||||
if isinstance(obj, bytes):
|
||||
return base64.b64encode(obj).decode("ascii")
|
||||
return obj
|
||||
|
||||
|
||||
def coerce_pg_native(obj: Any) -> Any:
|
||||
"""Recursively coerce PG-native types to JSON-safe equivalents.
|
||||
|
||||
Recurses into ``dict`` (stringifying keys, matching prior helper
|
||||
behavior) and ``list``/``tuple`` (tuples flatten to lists since JSON
|
||||
has no tuple type). Any other type passes through unchanged.
|
||||
"""
|
||||
if isinstance(obj, dict):
|
||||
return {str(k): coerce_pg_native(v) for k, v in obj.items()}
|
||||
if isinstance(obj, (list, tuple)):
|
||||
return [coerce_pg_native(v) for v in obj]
|
||||
return _coerce_scalar(obj)
|
||||
|
||||
|
||||
def decode_base64_bytes(value: Any) -> Any:
|
||||
"""Reverse ``coerce_pg_native``'s bytes-to-base64 step.
|
||||
|
||||
Useful at egress points that need the original bytes back (e.g.
|
||||
sending Gemini's ``thought_signature`` to the SDK on resume). Uses
|
||||
``validate=True`` so plain ASCII strings that happen to be
|
||||
permissively decodable (e.g. ``"abcd"``) are not silently turned
|
||||
into bytes — the original value passes through.
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
return base64.b64decode(value.encode("ascii"), validate=True)
|
||||
except (binascii.Error, ValueError):
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
class PGNativeJSONEncoder(json.JSONEncoder):
|
||||
"""``JSONEncoder`` covering UUID / datetime / date / Decimal / bytes.
|
||||
|
||||
Use as ``json.dumps(obj, cls=PGNativeJSONEncoder)``. Equivalent in
|
||||
coverage to :func:`coerce_pg_native` but skips the eager walk.
|
||||
"""
|
||||
|
||||
def default(self, obj: Any) -> Any:
|
||||
coerced = _coerce_scalar(obj)
|
||||
if coerced is obj:
|
||||
return super().default(obj)
|
||||
return coerced
|
||||
Reference in new issue
Block a user