Coverage for src/kwai/core/settings.py: 97%

101 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2024-01-01 00:00 +0000

1"""Module for the settings of this application.""" 

2 

3import os 

4import tomllib 

5 

6from functools import lru_cache 

7from typing import Annotated, Any, Literal, Optional, Union 

8 

9from pydantic import BaseModel, Field 

10 

11 

12ENV_SETTINGS_FILE = "KWAI_SETTINGS_FILE" 

13 

14 

15class SettingsException(Exception): 

16 """Raised when a problem occurred while loading the settings.""" 

17 

18 

19class ApplicationSettingsModel(BaseModel): 

20 """Settings that apply to all applications.""" 

21 

22 default: bool = False # Set to true for selecting the default application 

23 vite_server: str | None = None # Only required in development. 

24 variables: dict[str, Any] | None = None 

25 

26 

27class AdminApplicationSettings(ApplicationSettingsModel): 

28 """Settings for the admin application.""" 

29 

30 name: Literal["admin"] 

31 

32 

33class AuthenticationApplicationSettings(ApplicationSettingsModel): 

34 """Settings for the auth application.""" 

35 

36 name: Literal["auth"] 

37 

38 

39class AuthorApplicationSettings(ApplicationSettingsModel): 

40 """Settings for the author application.""" 

41 

42 name: Literal["author"] 

43 

44 

45class ClubApplicationSettings(ApplicationSettingsModel): 

46 """Settings for the club application.""" 

47 

48 name: Literal["club"] 

49 

50 

51class CoachApplicationSettings(ApplicationSettingsModel): 

52 """Settings for the portal application.""" 

53 

54 name: Literal["coach"] 

55 

56 

57class PortalApplicationSettings(ApplicationSettingsModel): 

58 """Settings for the portal application.""" 

59 

60 name: Literal["portal"] 

61 

62 

63Application = Annotated[ 

64 Union[ 

65 AdminApplicationSettings, 

66 AuthenticationApplicationSettings, 

67 AuthorApplicationSettings, 

68 ClubApplicationSettings, 

69 CoachApplicationSettings, 

70 PortalApplicationSettings, 

71 ], 

72 Field(discriminator="name"), 

73] 

74 

75 

76class FrontendSettings(BaseModel): 

77 """Settings for the frontend.""" 

78 

79 test: bool = False 

80 path: str 

81 apps: list[Application] 

82 

83 

84class FilesSettings(BaseModel): 

85 """Settings for files (upload).""" 

86 

87 path: str 

88 

89 

90class AdminSettings(BaseModel): 

91 """Settings for the administrator of the website.""" 

92 

93 name: str 

94 email: str 

95 

96 

97class ContactSettings(BaseModel): 

98 """Settings for the contact of the club.""" 

99 

100 street: str 

101 city: str 

102 email: str 

103 

104 

105class WebsiteSettings(BaseModel): 

106 """Settings about the website.""" 

107 

108 url: str 

109 email: str 

110 name: str 

111 copyright: Optional[str] = None 

112 admin: Optional[AdminSettings] = None 

113 contact: Optional[ContactSettings] = None 

114 

115 

116class DatabaseSettings(BaseModel): 

117 """Settings for the database connection.""" 

118 

119 host: str 

120 name: str 

121 user: str 

122 password: str 

123 

124 

125class CORSSettings(BaseModel): 

126 """Settings for configuring CORS.""" 

127 

128 origins: list[str] = Field(default_factory=list) 

129 methods: list[str] = Field(default_factory=lambda: ["*"]) 

130 headers: list[str] = Field(default_factory=lambda: ["*"]) 

131 

132 

133class LoggerSettings(BaseModel): 

134 """Settings for the logger.""" 

135 

136 file: str = "kwai.log" 

137 level: str = "DEBUG" 

138 retention: str = "7 days" 

139 rotation: str = "1 day" 

140 

141 

142class EmailSettings(BaseModel): 

143 """Settings for sending emails.""" 

144 

145 host: str 

146 port: int 

147 ssl: bool = True 

148 tls: bool = True 

149 user: str | None = None 

150 password: str | None = None 

151 address: str = Field(alias="from") 

152 

153 

154class RedisSettings(BaseModel): 

155 """Settings for Redis.""" 

156 

157 host: str = "127.0.0.1" 

158 port: int = 6379 

159 password: str | None = None 

160 logger: LoggerSettings | None = None 

161 

162 

163class GoogleSSOSettings(BaseModel): 

164 """Settings for Google SSO.""" 

165 

166 client_id: str 

167 client_secret: str 

168 

169 

170class SecuritySettings(BaseModel): 

171 """Setting or security.""" 

172 

173 access_token_expires_in: int = 60 # minutes 

174 refresh_token_expires_in: int = 43200 # 30 days 

175 jwt_algorithm: str = "HS256" 

176 jwt_secret: str 

177 jwt_refresh_secret: str 

178 

179 google: GoogleSSOSettings | None = None 

180 

181 

182class Settings(BaseModel): 

183 """Class with settings.""" 

184 

185 frontend: FrontendSettings 

186 

187 files: FilesSettings 

188 

189 security: SecuritySettings 

190 

191 logger: LoggerSettings | None = None 

192 

193 cors: CORSSettings | None = None 

194 

195 db: DatabaseSettings 

196 

197 website: WebsiteSettings 

198 

199 email: EmailSettings 

200 

201 redis: RedisSettings 

202 

203 

204@lru_cache 

205def get_settings() -> Settings: 

206 """Dependency function for creating the Settings instance. 

207 

208 The settings are cached with lru_cache, which means the file is only loaded ounce. 

209 

210 :raises: 

211 core.settings.SettingsException: Raised when the env variable is not set, or 

212 when the file 

213 can't be read. 

214 """ 

215 if ENV_SETTINGS_FILE in os.environ: 

216 settings_file = os.environ.get(ENV_SETTINGS_FILE, "") 

217 try: 

218 with open(settings_file, mode="rb") as file_handle: 

219 return Settings.model_validate(tomllib.load(file_handle)) 

220 except OSError as exc: 

221 raise SettingsException(f"Could not load {settings_file}") from exc 

222 raise SettingsException( 

223 f"{ENV_SETTINGS_FILE} should be set as environment variable" 

224 )