@@ -39,6 +39,7 @@ def parse_cors(v: Any) -> list[str] | str:
3939
4040class DatabaseSettings (BaseSettings ):
4141 """Database configuration settings."""
42+ model_config = SettingsConfigDict (env_prefix = "DATABASE_" )
4243
4344 POSTGRES_SERVER : str = "localhost"
4445 POSTGRES_USER : str = "postgres"
@@ -78,6 +79,7 @@ def ASYNC_SQLALCHEMY_DATABASE_URI(self) -> str:
7879
7980class AuthSettings (BaseSettings ):
8081 """Authentication and authorization settings."""
82+ model_config = SettingsConfigDict (env_prefix = "AUTH_" )
8183
8284 SECRET_KEY : str = secrets .token_urlsafe (32 )
8385 ALGORITHM : str = "HS256"
@@ -108,9 +110,9 @@ class AuthSettings(BaseSettings):
108110 SESSION_COOKIE_DOMAIN : Optional [str ] = None
109111
110112 # CORS
111- BACKEND_CORS_ORIGINS : list [AnyUrl ] = [
112- "http://localhost:3000" ,
113- "http://localhost:8000" ,
113+ BACKEND_CORS_ORIGINS : list [HttpUrl ] = [
114+ HttpUrl ( "http://localhost:3000" ) ,
115+ HttpUrl ( "http://localhost:8000" ) ,
114116 ]
115117
116118 @property
@@ -121,6 +123,7 @@ def all_cors_origins(self) -> list[str]:
121123
122124class EmailSettings (BaseSettings ):
123125 """Email configuration settings."""
126+ model_config = SettingsConfigDict (env_prefix = "EMAIL_" )
124127
125128 SMTP_TLS : bool = True
126129 SMTP_PORT : int = 587
@@ -138,6 +141,7 @@ def EMAILS_ENABLED(self) -> bool:
138141
139142class RedisSettings (BaseSettings ):
140143 """Redis configuration settings."""
144+ model_config = SettingsConfigDict (env_prefix = "REDIS_" )
141145
142146 REDIS_HOST : str = "localhost"
143147 REDIS_PORT : int = 6379
@@ -159,10 +163,6 @@ def REDIS_URL(self) -> RedisDsn:
159163 )
160164
161165
162- class Settings (DatabaseSettings , AuthSettings , EmailSettings , RedisSettings ):
163- """Application settings."""
164-
165-
166166def parse_cors (v : Any ) -> list [str ] | str :
167167 if isinstance (v , str ) and not v .startswith ("[" ):
168168 return [i .strip () for i in v .split ("," )]
@@ -171,7 +171,8 @@ def parse_cors(v: Any) -> list[str] | str:
171171 raise ValueError (v )
172172
173173
174- class Settings (BaseSettings ):
174+ class Settings (DatabaseSettings , AuthSettings , EmailSettings , RedisSettings ):
175+ """Application settings."""
175176 model_config = SettingsConfigDict (
176177 env_file = PROJECT_ROOT / ".env" ,
177178 env_file_encoding = "utf-8" ,
@@ -184,6 +185,15 @@ class Settings(BaseSettings):
184185 PROJECT_NAME : str = "Copilot API"
185186 API_V1_STR : str = "/api/v1"
186187 ENVIRONMENT : Literal ["local" , "staging" , "production" ] = "local"
188+
189+ # Allow development as an alias for local
190+ @model_validator (mode = 'before' )
191+ @classmethod
192+ def validate_environment (cls , data : Any ) -> Any :
193+ if isinstance (data , dict ) and data .get ('ENVIRONMENT' ) == 'development' :
194+ data ['ENVIRONMENT' ] = 'local'
195+ return data
196+
187197 DEBUG : bool = False
188198
189199 # Security
@@ -255,27 +265,36 @@ def set_debug(cls, v: str) -> str:
255265 return v
256266
257267 @field_validator ("BACKEND_CORS_ORIGINS" , mode = "before" )
258- def assemble_cors_origins (cls , v : Union [str , list [str ]] ) -> list [str ] | str :
268+ def assemble_cors_origins (cls , v : Union [str , list [Union [ str , HttpUrl ]]] ) -> list [HttpUrl ] :
259269 """Parse CORS origins from a comma-separated string or list."""
260- if isinstance (v , str ) and not v .startswith ("[" ):
261- return [i .strip () for i in v .split ("," )]
262- elif isinstance (v , list ):
263- return v
264- raise ValueError (v )
265-
266- class Config :
267- case_sensitive = True
268- env_file = ".env"
269- env_file_encoding = "utf-8"
270-
270+ if isinstance (v , str ):
271+ if v .startswith ("[" ):
272+ # Handle JSON array string
273+ import json
274+ v = json .loads (v )
275+ else :
276+ # Handle comma-separated string
277+ v = [i .strip () for i in v .split ("," )]
278+
279+ # Convert all items to HttpUrl objects
280+ result = []
281+ for item in v :
282+ if isinstance (item , str ):
283+ result .append (HttpUrl (item ))
284+ elif isinstance (item , HttpUrl ):
285+ result .append (item )
286+ else :
287+ raise ValueError (f"Invalid CORS origin: { item } " )
288+ return result
289+
271290 PROJECT_NAME : str
272291 SENTRY_DSN : HttpUrl | None = None
273- POSTGRES_SERVER : str
292+ POSTGRES_SERVER : str = "localhost"
293+ POSTGRES_USER : str = "postgres"
294+ POSTGRES_PASSWORD : str = "postgres"
295+ POSTGRES_DB : str = "copilot"
274296 POSTGRES_PORT : int = 5432
275- POSTGRES_USER : str
276- POSTGRES_PASSWORD : str = ""
277- POSTGRES_DB : str = ""
278-
297+
279298 @computed_field # type: ignore[prop-decorator]
280299 @property
281300 def SQLALCHEMY_DATABASE_URI (self ) -> PostgresDsn :
@@ -295,7 +314,7 @@ def SQLALCHEMY_DATABASE_URI(self) -> PostgresDsn:
295314 SMTP_USER : str | None = None
296315 SMTP_PASSWORD : str | None = None
297316 EMAILS_FROM_EMAIL : EmailStr | None = None
298- EMAILS_FROM_NAME : EmailStr | None = None
317+ EMAILS_FROM_NAME : str | None = None
299318
300319 @model_validator (mode = "after" )
301320 def _set_default_emails_from (self ) -> Self :
0 commit comments