42 lines
1.4 KiB
Python
42 lines
1.4 KiB
Python
"""Small persistence helpers for safer SQL text usage.
|
|
|
|
ClientFlow still uses SQLAlchemy ``text()`` for PostgreSQL-specific JSONB and
|
|
reconciliation queries. These helpers standardise JSONB patches so new code can
|
|
prefer ``metadata = metadata || CAST(:metadata_patch AS jsonb)`` over fragile
|
|
``jsonb_build_object('x', :param)`` patterns.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import date, datetime, timezone
|
|
from decimal import Decimal
|
|
from typing import Any, Dict, Mapping, Optional
|
|
from uuid import UUID
|
|
|
|
|
|
def json_default(value: Any) -> Any:
|
|
if isinstance(value, (datetime, date)):
|
|
return value.isoformat()
|
|
if isinstance(value, UUID):
|
|
return str(value)
|
|
if isinstance(value, Decimal):
|
|
# Use string to avoid float rounding in money fields.
|
|
return str(value)
|
|
if isinstance(value, set):
|
|
return sorted(str(item) for item in value)
|
|
return str(value)
|
|
|
|
|
|
def jsonb_param(value: Any) -> str:
|
|
"""Return a JSON string suitable for ``CAST(:param AS jsonb)``."""
|
|
return json.dumps(value if value is not None else {}, ensure_ascii=False, default=json_default)
|
|
|
|
|
|
def metadata_patch(**items: Any) -> Dict[str, Any]:
|
|
"""Create a small metadata patch with an ISO timestamp when requested."""
|
|
return {key: value for key, value in items.items() if value is not None}
|
|
|
|
|
|
def utc_now_text() -> str:
|
|
return datetime.now(timezone.utc).isoformat()
|