Shared helpers used across routers and middleware. This is where persistence, cross-cutting infra calls, and setup logic live — anything a router shouldn't have to know the details of.
opensearch_util.py—OpenSearchUtil, the single OpenSearch client wrapper. Holds methods likeget_team_document_uris(team_id),insert_document(index, body),log_audit_event(...). All OpenSearch access should go through here, not through ad-hocopensearch_pyclient instantiation elsewhere.aws_knowledge_base.py— helpers for managing the Bedrock Knowledge Base data source sync lifecycle (as distinct fromclient/amazon_bedrock.py, which handles retrieve/generate at query time — this handles ingestion-side sync).email_utils.py— rendersapi/templates/*.htmlwith Jinja2 and sends via SMTP.logging_utils.py—setup_logging(), called once inapp.pyat startup; configures logging fromlog_config.yaml.
- Anything that talks to OpenSearch more than once across the codebase belongs as a method on
OpenSearchUtil, not copy-pastedclient.search(...)calls in multiple routers. - Utils should raise
APIExceptionon failure just like routers do (seeapi/exceptions/README.md) — don't let a rawopensearch_pyorboto3exception bubble up unwrapped. - Keep
logging_utils.py'ssetup_logging()idempotent — it's called once at import time inapp.py; don't add per-request logging setup here. - If you add a new utils module, name it
<concern>_util.pyor<concern>_utils.pymatching the existing pattern, and instantiate any client-like object once at module or router level (seeopensearch_client = OpenSearchUtil()in routers) rather than per-request.