-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.py
More file actions
141 lines (123 loc) · 5.12 KB
/
Copy pathconfig.py
File metadata and controls
141 lines (123 loc) · 5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
from functools import lru_cache
import json
from typing import Any, List, Optional, Union
from pydantic import Field, HttpUrl, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Application configuration loaded from environment variables."""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
api_v1_str: str = Field("/api/v1", validation_alias="API_V1_STR")
project_name: str = Field("Commitly Backend", validation_alias="PROJECT_NAME")
debug: bool = Field(False, validation_alias="DEBUG")
supabase_url: HttpUrl = Field(..., validation_alias="SUPABASE_URL")
supabase_anon_key: str = Field(..., validation_alias="SUPABASE_ANON_KEY")
database_url: str = Field(..., validation_alias="DATABASE_URL")
# Clerk (authentication) configuration
clerk_jwks_url: HttpUrl = Field(..., validation_alias="CLERK_JWKS_URL")
clerk_issuer: str = Field(..., validation_alias="CLERK_ISSUER")
clerk_audience: str = Field(..., validation_alias="CLERK_AUDIENCE")
clerk_authorized_parties: List[str] = Field(
default_factory=list, validation_alias="CLERK_AUTHORIZED_PARTIES"
)
clerk_jwks_cache_seconds: int = Field(
300, validation_alias="CLERK_JWKS_CACHE_SECONDS"
)
allowed_origins: Union[str, List[str]] = Field(
default="*",
validation_alias="ALLOWED_ORIGINS",
)
frontend_origin: Optional[HttpUrl] = Field(
default=None,
validation_alias="FRONTEND_ORIGIN",
)
# GitHub ingestion
github_api_base: HttpUrl = Field( # type: ignore
"https://api.github.com", validation_alias="GITHUB_API_BASE"
)
github_token: Optional[str] = Field(default=None, validation_alias="GITHUB_TOKEN")
github_commit_limit: int = Field(20000, validation_alias="GITHUB_COMMIT_LIMIT")
github_oauth_client_id: Optional[str] = Field(
default=None, validation_alias="GITHUB_OAUTH_CLIENT_ID"
)
github_oauth_client_secret: Optional[str] = Field(
default=None, validation_alias="GITHUB_OAUTH_CLIENT_SECRET"
)
github_oauth_redirect_uri: Optional[HttpUrl] = Field(
default=None, validation_alias="GITHUB_OAUTH_REDIRECT_URI"
)
github_oauth_success_redirect: Optional[HttpUrl] = Field(
default=None, validation_alias="GITHUB_OAUTH_SUCCESS_REDIRECT"
)
github_oauth_scope: str = Field(
"read:user public_repo", validation_alias="GITHUB_OAUTH_SCOPE"
)
# Gemini + AI pipeline
gemini_api_key: Optional[str] = Field(
default=None, validation_alias="GEMINI_API_KEY"
)
gemini_model: str = Field("gemini-flash-latest", validation_alias="GEMINI_MODEL")
roadmap_timeline_fraction: float = Field(
0.25, ge=0.1, le=1.0, validation_alias="ROADMAP_TIMELINE_FRACTION"
)
# Redis cache
redis_url: Optional[str] = Field(default=None, validation_alias="REDIS_URL")
roadmap_cache_ttl_seconds: int = Field(
3600, validation_alias="ROADMAP_CACHE_TTL_SECONDS"
)
@staticmethod
def _coerce_list(value: Any) -> List[str]:
match value:
case None:
return []
case str() as raw:
cleaned = raw.strip()
if not cleaned:
return []
if cleaned.startswith("["):
try:
data = json.loads(cleaned)
if isinstance(data, list):
return [
item.strip()
for item in data
if isinstance(item, str) and item.strip()
]
except json.JSONDecodeError:
pass
return [item.strip() for item in cleaned.split(",") if item.strip()]
case list() as sequence:
return [
item.strip()
for item in sequence
if isinstance(item, str) and item.strip()
]
return []
@field_validator("allowed_origins", mode="before")
@classmethod
def parse_origins(cls, value: Any) -> List[str]:
# Handle empty string or None explicitly to prevent JSON parsing errors
if value is None:
return ["*"]
# Handle empty string
if isinstance(value, str) and not value.strip():
return ["*"]
# If it's already a list, return as-is (or default to ["*"])
if isinstance(value, list):
return value if value else ["*"]
# Try to parse as string (handles comma-separated or JSON)
parsed = cls._coerce_list(value)
return parsed or ["*"]
@field_validator("clerk_authorized_parties", mode="before")
@classmethod
def parse_authorized_parties(cls, value: Any) -> List[str]:
return cls._coerce_list(value)
@lru_cache
def get_settings() -> Settings:
"""Return a cached instance of the application settings."""
return Settings() # type: ignore
settings = get_settings()