Load .env via python-dotenv so extractor gets API keys reliably

This commit is contained in:
Himanshu Dongre 2026-04-12 13:11:57 +05:30
parent bea192d25d
commit fa62e2eff3
4 changed files with 45 additions and 2 deletions

View file

@ -1,11 +1,24 @@
# Smriti - Environment Variables
# Copy this file to .env and fill in the values
# Copy this file to .env and fill in the values.
#
# The backend loads this file via python-dotenv at startup so API keys
# are available even when the server is launched by a process that does
# not inherit your shell profile (e.g. uvicorn from an IDE, a CI
# runner, or a subprocess-based tool host like Claude Code).
#
# Existing env vars are NOT overridden — if you also export a key in
# your shell profile, the shell value takes precedence.
# Database
DATABASE_URL=postgresql://smriti:smriti@localhost:5432/smriti
# OpenAI (leave empty to use mock provider)
# Provider API keys — set the ones you want to use.
# Leave empty to fall back to config/providers.yaml or mock provider.
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
OPENROUTER_API_KEY=
# Default model for background intelligence (extraction, draft, review)
OPENAI_MODEL=gpt-4o-mini
# App

View file

@ -1,6 +1,12 @@
"""
Provider configuration loader.
Priority: env vars > config/providers.yaml > built-in defaults.
The .env file (at the project root, one level above backend/) is loaded
via python-dotenv at import time so that API keys set there are visible
to os.environ.get() even when the backend is started by a process that
does not inherit the user's shell profile (e.g. uvicorn launched from
an IDE, a CI runner, or a subprocess-based tool host).
"""
from __future__ import annotations
@ -8,6 +14,19 @@ import os
from dataclasses import dataclass, field
from pathlib import Path
from dotenv import load_dotenv
# Load .env from the project root (smriti/) — one level above
# backend/. Falls back silently if the file does not exist (e.g. in
# Docker where env vars are injected by the runtime).
_PROJECT_ROOT_ENV = Path(__file__).parent.parent.parent / ".env"
_BACKEND_DIR_ENV = Path(__file__).parent.parent / ".env"
# Try project root first (where .env.example lives), then backend dir.
if _PROJECT_ROOT_ENV.is_file():
load_dotenv(_PROJECT_ROOT_ENV, override=False)
elif _BACKEND_DIR_ENV.is_file():
load_dotenv(_BACKEND_DIR_ENV, override=False)
try:
import yaml
_YAML_AVAILABLE = True

View file

@ -2,10 +2,13 @@
from __future__ import annotations
import json as _json
import logging
from app.config_loader import get_provider_config, ProviderNotConfiguredError
from app.providers.base import ProviderAdapter
logger = logging.getLogger(__name__)
# Canned JSON response returned by the mock adapter when the caller asks
# for JSON mode (response_format={"type": "json_object"}). Covers every
@ -74,6 +77,13 @@ def get_adapter(provider: str, allow_mock: bool = False) -> ProviderAdapter:
cfg = get_provider_config(p)
except ProviderNotConfiguredError:
if allow_mock:
logger.warning(
"Provider '%s' has no API key configured — falling back to "
"MockAdapter. Extraction, drafting, and review endpoints will "
"return deterministic mock content instead of real LLM output. "
"Set the API key in .env or config/providers.yaml to fix this.",
provider,
)
return MockAdapter()
raise

View file

@ -16,6 +16,7 @@ dependencies = [
"anthropic>=0.40.0",
"pyyaml>=6.0",
"pgvector>=0.2.0",
"python-dotenv>=1.0.0",
]
[project.optional-dependencies]