Skip to content

Commit 36e3fbb

Browse files
AkhileshNegiAkhilesh Negi
authored andcommitted
Testing: overriding env with test env (#321)
* getting rid of unused variables * getting rid of unused variables * loading variables * loading engine * creating more pools * adding example * updating gitignore * cleanup variables * cleanups * cleanups * skipping warning in development and testing * skipping for testing and development * updating default keys * updating default keys * WIP towards using get_setting * updating script * removing LRU cache * renaming variables --------- Co-authored-by: Akhilesh Negi <akhileshnegi@Akhileshs-MacBook-Pro.local>
1 parent dccf023 commit 36e3fbb

13 files changed

Lines changed: 123 additions & 47 deletions

File tree

.env.example

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,3 @@ AWS_ACCESS_KEY_ID=
4444
AWS_SECRET_ACCESS_KEY=
4545
AWS_DEFAULT_REGION=ap-south-1
4646
AWS_S3_BUCKET_PREFIX="bucket-prefix-name"
47-
48-
# OpenAI
49-
50-
OPENAI_API_KEY="this_is_not_a_secret"
51-
LANGFUSE_PUBLIC_KEY="this_is_not_a_secret"
52-
LANGFUSE_SECRET_KEY="this_is_not_a_secret"
53-
LANGFUSE_HOST="this_is_not_a_secret"
54-
55-
# Misc
56-
57-
CI=""

.env.test.example

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
ENVIRONMENT=testing
2+
3+
PROJECT_NAME="AI Platform"
4+
STACK_NAME=ai-platform
5+
6+
#Backend
7+
SECRET_KEY=changethis
8+
FIRST_SUPERUSER=superuser@example.com
9+
FIRST_SUPERUSER_PASSWORD=changethis
10+
EMAIL_TEST_USER="test@example.com"
11+
12+
# Postgres
13+
14+
POSTGRES_SERVER=localhost
15+
POSTGRES_PORT=5432
16+
POSTGRES_DB=ai_platform_test
17+
POSTGRES_USER=postgres
18+
POSTGRES_PASSWORD=postgres
19+
20+
# Configure these with your own Docker registry images
21+
22+
DOCKER_IMAGE_BACKEND=backend
23+
DOCKER_IMAGE_FRONTEND=frontend
24+
25+
# AWS
26+
27+
AWS_ACCESS_KEY_ID=this_is_a_test_key
28+
AWS_SECRET_ACCESS_KEY=this_is_a_test_key
29+
AWS_DEFAULT_REGION=ap-south-1
30+
AWS_S3_BUCKET_PREFIX="bucket-prefix-name"

.github/workflows/benchmark.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@ jobs:
1818
count: [100]
1919

2020
env:
21-
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
22-
LANGFUSE_PUBLIC_KEY: ${{ secrets.LANGFUSE_PUBLIC_KEY }}
23-
LANGFUSE_SECRET_KEY: ${{ secrets.LANGFUSE_SECRET_KEY }}
24-
LANGFUSE_HOST: ${{ secrets.LANGFUSE_HOST }}
2521
LOCAL_CREDENTIALS_ORG_OPENAI_API_KEY: ${{ secrets.LOCAL_CREDENTIALS_ORG_OPENAI_API_KEY }}
2622
LOCAL_CREDENTIALS_API_KEY: ${{ secrets.LOCAL_CREDENTIALS_API_KEY }}
2723

.github/workflows/continuous_integration.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
env:
1616
POSTGRES_USER: postgres
1717
POSTGRES_PASSWORD: postgres
18-
POSTGRES_DB: ai_platform
18+
POSTGRES_DB: ai_platform_test
1919
ports:
2020
- 5432:5432
2121
options: --health-cmd "pg_isready -U postgres" --health-interval 10s --health-timeout 5s --health-retries 5
@@ -34,7 +34,9 @@ jobs:
3434
python-version: ${{ matrix.python-version }}
3535

3636
- name: Making env file
37-
run: cp .env.example .env
37+
run: |
38+
cp .env.test.example .env
39+
cp .env.test.example .env.test
3840
3941
- name: Install uv
4042
uses: astral-sh/setup-uv@v6

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ node_modules/
66
/playwright/.cache/
77

88
# Environments
9-
.env
9+
.env*
1010
.venv
1111
env/
1212
venv/

backend/app/api/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@
3939
api_router.include_router(utils.router)
4040
api_router.include_router(doc_transformation_job.router)
4141

42-
if settings.ENVIRONMENT == "local":
42+
if settings.ENVIRONMENT in ["development", "testing"]:
4343
api_router.include_router(private.router)

backend/app/core/config.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import secrets
22
import warnings
33
import os
4-
from typing import Annotated, Any, Literal
4+
from typing import Any, Literal
55

66
from pydantic import (
77
EmailStr,
@@ -15,30 +15,30 @@
1515
from typing_extensions import Self
1616

1717

18-
def parse_cors(v: Any) -> list[str] | str:
19-
if isinstance(v, str) and not v.startswith("["):
20-
return [i.strip() for i in v.split(",")]
21-
elif isinstance(v, list | str):
22-
return v
23-
raise ValueError(v)
18+
def parse_cors(origins: Any) -> list[str] | str:
19+
# If it's a plain comma-separated string, split it into a list
20+
if isinstance(origins, str) and not origins.startswith("["):
21+
return [origin.strip() for origin in origins.split(",")]
22+
# If it's already a list or JSON-style string, just return it
23+
elif isinstance(origins, (list, str)):
24+
return origins
25+
raise ValueError(f"Invalid CORS origins format: {origins!r}")
2426

2527

2628
class Settings(BaseSettings):
2729
model_config = SettingsConfigDict(
28-
# Use top level .env file (one level above ./backend/)
29-
env_file="../.env",
30+
# env_file will be set dynamically in get_settings()
3031
env_ignore_empty=True,
3132
extra="ignore",
3233
)
33-
LANGFUSE_PUBLIC_KEY: str
34-
LANGFUSE_SECRET_KEY: str
35-
LANGFUSE_HOST: str # 🇪🇺 EU region
36-
OPENAI_API_KEY: str
34+
3735
API_V1_STR: str = "/api/v1"
3836
SECRET_KEY: str = secrets.token_urlsafe(32)
3937
# 60 minutes * 24 hours * 1 days = 1 days
4038
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 1
41-
ENVIRONMENT: Literal["local", "staging", "production"] = "local"
39+
ENVIRONMENT: Literal[
40+
"development", "testing", "staging", "production"
41+
] = "development"
4242

4343
PROJECT_NAME: str
4444
SENTRY_DSN: HttpUrl | None = None
@@ -84,7 +84,7 @@ def _check_default_secret(self, var_name: str, value: str | None) -> None:
8484
f'The value of {var_name} is "changethis", '
8585
"for security, please change it, at least for deployments."
8686
)
87-
if self.ENVIRONMENT == "local":
87+
if self.ENVIRONMENT in ["development", "testing"]:
8888
warnings.warn(message, stacklevel=1)
8989
else:
9090
raise ValueError(message)
@@ -100,4 +100,17 @@ def _enforce_non_default_secrets(self) -> Self:
100100
return self
101101

102102

103-
settings = Settings() # type: ignore
103+
def get_settings() -> Settings:
104+
"""Get settings with appropriate env file based on ENVIRONMENT."""
105+
environment = os.getenv("ENVIRONMENT", "development")
106+
107+
# Determine env file
108+
env_files = {"testing": "../.env.test", "development": "../.env"}
109+
env_file = env_files.get(environment, "../.env")
110+
111+
# Create Settings instance with the appropriate env file
112+
return Settings(_env_file=env_file)
113+
114+
115+
# Export settings instance
116+
settings = get_settings()

backend/app/core/db.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
11
from sqlmodel import Session, create_engine, select
22

33
from app import crud
4-
from app.core.config import settings
54
from app.models import User, UserCreate
65

7-
engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI))
6+
7+
def get_engine():
8+
"""Get database engine with current settings."""
9+
# Import settings dynamically to get the current instance
10+
from app.core.config import settings
11+
12+
# Configure connection pool settings
13+
# For testing, we need more connections since tests run in parallel
14+
pool_size = 20 if settings.ENVIRONMENT == "development" else 5
15+
max_overflow = 30 if settings.ENVIRONMENT == "development" else 10
16+
17+
return create_engine(
18+
str(settings.SQLALCHEMY_DATABASE_URI),
19+
pool_size=pool_size,
20+
max_overflow=max_overflow,
21+
pool_pre_ping=True,
22+
pool_recycle=300, # Recycle connections after 5 minutes
23+
)
24+
25+
26+
# Create a default engine for backward compatibility
27+
engine = get_engine()
828

929

1030
# make sure all SQLModel models are imported (app.models) before initializing DB

backend/app/load_env.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import os
2+
from dotenv import load_dotenv
3+
4+
5+
def load_environment():
6+
env = os.getenv("ENVIRONMENT", "development")
7+
8+
# Use the same path as config.py expects (one level above backend/)
9+
env_file = "../.env"
10+
if env == "testing":
11+
env_file = "../.env.test"
12+
13+
load_dotenv(env_file)

backend/app/main.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
import sentry_sdk
2+
23
from fastapi import FastAPI
34
from fastapi.routing import APIRoute
45
from asgi_correlation_id.middleware import CorrelationIdMiddleware
56
from app.api.main import api_router
67
from app.core.config import settings
7-
import app.core.logger
88
from app.core.exception_handlers import register_exception_handlers
99
from app.core.middleware import http_request_logger
1010

11+
from app.load_env import load_environment
12+
13+
# Load environment variables
14+
load_environment()
15+
1116

1217
def custom_generate_unique_id(route: APIRoute) -> str:
1318
return f"{route.tags[0]}-{route.name}"
1419

1520

16-
if settings.SENTRY_DSN and settings.ENVIRONMENT != "local":
21+
if settings.SENTRY_DSN and settings.ENVIRONMENT != "development":
1722
sentry_sdk.init(dsn=str(settings.SENTRY_DSN), enable_tracing=True)
1823

1924
app = FastAPI(

0 commit comments

Comments
 (0)