11import secrets
22import warnings
3- from typing import Annotated , Any , Literal
4-
5- from pydantic import (
6- AnyUrl ,
7- BeforeValidator ,
8- EmailStr ,
9- HttpUrl ,
10- PostgresDsn ,
11- computed_field ,
12- model_validator ,
13- )
3+ from typing import Any , Annotated , Literal
4+
5+ from pydantic import AnyUrl , BeforeValidator , EmailStr , HttpUrl , computed_field , model_validator
146from pydantic_settings import BaseSettings , SettingsConfigDict
157from typing_extensions import Self
8+ from pydantic import AnyUrl , HttpUrl , EmailStr
169
1710
1811def parse_cors (v : Any ) -> list [str ] | str :
12+ """Parses CORS origins from env or defaults."""
1913 if isinstance (v , str ) and not v .startswith ("[" ):
2014 return [i .strip () for i in v .split ("," ) if i .strip ()]
21- elif isinstance (v , list | str ):
15+ elif isinstance (v , ( list , str ) ):
2216 return v
2317 raise ValueError (v )
2418
2519
2620class Settings (BaseSettings ):
21+ """
22+ Application settings (no .env required).
23+ Uses SQLite instead of PostgreSQL.
24+ """
25+
2726 model_config = SettingsConfigDict (
28- # Use top level .env file (one level above ./backend/)
29- env_file = "../.env" ,
3027 env_ignore_empty = True ,
3128 extra = "ignore" ,
3229 )
30+
31+ # --- Core app settings ---
3332 API_V1_STR : str = "/api/v1"
33+ PROJECT_NAME : str = "My SQLite App"
3434 SECRET_KEY : str = secrets .token_urlsafe (32 )
35- # 60 minutes * 24 hours * 8 days = 8 days
36- ACCESS_TOKEN_EXPIRE_MINUTES : int = 60 * 24 * 8
35+ ACCESS_TOKEN_EXPIRE_MINUTES : int = 60 * 24 * 8 # 8 days
3736 FRONTEND_HOST : str = "http://localhost:5173"
3837 ENVIRONMENT : Literal ["local" , "staging" , "production" ] = "local"
38+ SENTRY_DSN : HttpUrl | None = None # ✅ Added this line
3939
40+
41+ # --- CORS ---
4042 BACKEND_CORS_ORIGINS : Annotated [
4143 list [AnyUrl ] | str , BeforeValidator (parse_cors )
4244 ] = []
4345
44- @computed_field # type: ignore[prop-decorator]
46+ @computed_field
4547 @property
4648 def all_cors_origins (self ) -> list [str ]:
49+ """Combine backend and frontend origins for CORS."""
4750 return [str (origin ).rstrip ("/" ) for origin in self .BACKEND_CORS_ORIGINS ] + [
4851 self .FRONTEND_HOST
4952 ]
5053
51- PROJECT_NAME : str
52- SENTRY_DSN : HttpUrl | None = None
53- POSTGRES_SERVER : str
54- POSTGRES_PORT : int = 5432
55- POSTGRES_USER : str
56- POSTGRES_PASSWORD : str = ""
57- POSTGRES_DB : str = ""
54+ # --- Database (SQLite only) ---
55+ SQLITE_DB_PATH : str = "./app.db"
5856
59- @computed_field # type: ignore[prop-decorator]
57+ @computed_field
6058 @property
61- def SQLALCHEMY_DATABASE_URI (self ) -> PostgresDsn :
62- return PostgresDsn .build (
63- scheme = "postgresql+psycopg" ,
64- username = self .POSTGRES_USER ,
65- password = self .POSTGRES_PASSWORD ,
66- host = self .POSTGRES_SERVER ,
67- port = self .POSTGRES_PORT ,
68- path = self .POSTGRES_DB ,
69- )
59+ def SQLALCHEMY_DATABASE_URI (self ) -> str :
60+ """SQLAlchemy connection string for SQLite."""
61+ return f"sqlite:///{ self .SQLITE_DB_PATH } "
7062
63+ # --- Email (optional) ---
7164 SMTP_TLS : bool = True
7265 SMTP_SSL : bool = False
7366 SMTP_PORT : int = 587
@@ -85,21 +78,19 @@ def _set_default_emails_from(self) -> Self:
8578
8679 EMAIL_RESET_TOKEN_EXPIRE_HOURS : int = 48
8780
88- @computed_field # type: ignore[prop-decorator]
81+ @computed_field
8982 @property
9083 def emails_enabled (self ) -> bool :
9184 return bool (self .SMTP_HOST and self .EMAILS_FROM_EMAIL )
9285
86+ # --- Superuser ---
9387 EMAIL_TEST_USER :
EmailStr = "[email protected] " 94- FIRST_SUPERUSER : EmailStr
95- FIRST_SUPERUSER_PASSWORD : str
88+ FIRST_SUPERUSER :
EmailStr = "[email protected] " 89+ FIRST_SUPERUSER_PASSWORD : str = "changeme"
9690
9791 def _check_default_secret (self , var_name : str , value : str | None ) -> None :
98- if value == "changethis" :
99- message = (
100- f'The value of { var_name } is "changethis", '
101- "for security, please change it, at least for deployments."
102- )
92+ if value == "changeme" :
93+ message = f'The value of { var_name } is "changeme", please change it.'
10394 if self .ENVIRONMENT == "local" :
10495 warnings .warn (message , stacklevel = 1 )
10596 else :
@@ -108,12 +99,9 @@ def _check_default_secret(self, var_name: str, value: str | None) -> None:
10899 @model_validator (mode = "after" )
109100 def _enforce_non_default_secrets (self ) -> Self :
110101 self ._check_default_secret ("SECRET_KEY" , self .SECRET_KEY )
111- self ._check_default_secret ("POSTGRES_PASSWORD" , self .POSTGRES_PASSWORD )
112- self ._check_default_secret (
113- "FIRST_SUPERUSER_PASSWORD" , self .FIRST_SUPERUSER_PASSWORD
114- )
115-
102+ self ._check_default_secret ("FIRST_SUPERUSER_PASSWORD" , self .FIRST_SUPERUSER_PASSWORD )
116103 return self
117104
118105
106+ # Instantiate settings immediately
119107settings = Settings () # type: ignore
0 commit comments