Skip to content

Commit 130b962

Browse files
refactor: structured configuration for allauth
this is a bit more than merely a refactoring: now you can override the entire configuration from outside (e.g. using Nix attrsets) without touching secret values. also the type declaration allows inferring the shape of allowed values without having to check allauth documentation.
1 parent c88ecd1 commit 130b962

1 file changed

Lines changed: 40 additions & 31 deletions

File tree

src/website/tracker/settings.py

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,27 @@
1414
import sys
1515
from os import environ as env
1616
from pathlib import Path
17+
from typing import Annotated
1718

1819
import dj_database_url
1920
import sentry_sdk
20-
from pydantic import BaseModel, DirectoryPath, Field
21+
from pydantic import BaseModel, DirectoryPath, Field, PlainSerializer
2122
from pydantic_settings import BaseSettings, SettingsConfigDict
2223
from sentry_sdk.integrations.django import DjangoIntegration
2324

2425

26+
def get_secret(name: str, encoding: str = "utf-8") -> str:
27+
credentials_dir = Secrets().CREDENTIALS_DIRECTORY # type: ignore[reportCallIssue]
28+
29+
try:
30+
with open(f"{credentials_dir}/{name}", encoding=encoding) as f:
31+
secret = f.read().removesuffix("\n")
32+
except FileNotFoundError:
33+
raise RuntimeError(f"No secret named {name} found in {credentials_dir}.")
34+
35+
return secret
36+
37+
2538
class Secrets(BaseSettings):
2639
CREDENTIALS_DIRECTORY: DirectoryPath
2740

@@ -90,32 +103,43 @@ class DjangoSettings(BaseModel):
90103
default=[],
91104
)
92105

93-
DJANGO_SETTINGS: DjangoSettings
106+
class SocialAccountProviders(BaseModel):
107+
class GitHub(BaseModel):
108+
SCOPE: list[str] = Field(
109+
description="Access scopes required by the application"
110+
)
94111

112+
class AppSettings(BaseModel):
113+
client_id: Annotated[str, PlainSerializer(get_secret)]
114+
secret: Annotated[str, PlainSerializer(get_secret)]
115+
key: str = ""
95116

96-
for key, value in Settings().dict()["DJANGO_SETTINGS"].items(): # type: ignore[reportCallIssue]
97-
setattr(sys.modules[__name__], key, value)
117+
APPS: list[AppSettings] = []
98118

99-
# TODO(@fricklerhandwerk): move all configuration over to pydantic-settings
119+
github: GitHub | None
100120

101-
# Build paths inside the project like this: BASE_DIR / 'subdir'.
102-
BASE_DIR = Path(__file__).resolve().parent.parent
121+
_GitHub = SocialAccountProviders.GitHub
122+
_App = SocialAccountProviders.GitHub.AppSettings
103123

124+
SOCIALACCOUNT_PROVIDERS: SocialAccountProviders = SocialAccountProviders(
125+
github=_GitHub(
126+
SCOPE=["read:user", "read:org"],
127+
APPS=[_App(client_id="GH_CLIENT_ID", secret="GH_SECRET")],
128+
),
129+
)
104130

105-
def get_secret(name: str, encoding: str = "utf-8") -> str:
106-
credentials_dir = Secrets().CREDENTIALS_DIRECTORY # type: ignore[reportCallIssue]
131+
DJANGO_SETTINGS: DjangoSettings
107132

108-
try:
109-
with open(f"{credentials_dir}/{name}", encoding=encoding) as f:
110-
secret = f.read().removesuffix("\n")
111-
except FileNotFoundError:
112-
raise RuntimeError(f"No secret named {name} found in {credentials_dir}.")
113133

114-
return secret
134+
for key, value in Settings().model_dump()["DJANGO_SETTINGS"].items(): # type: ignore[reportCallIssue]
135+
setattr(sys.modules[__name__], key, value)
115136

137+
# TODO(@fricklerhandwerk): move all configuration over to pydantic-settings
116138

117-
## GlitchTip setup
139+
# Build paths inside the project like this: BASE_DIR / 'subdir'.
140+
BASE_DIR = Path(__file__).resolve().parent.parent
118141

142+
## GlitchTip setup
119143
if "GLITCHTIP_DSN" in env:
120144
sentry_sdk.init(
121145
dsn=get_secret("GLITCHTIP_DSN"),
@@ -312,21 +336,6 @@ def get_secret(name: str, encoding: str = "utf-8") -> str:
312336
"allauth.account.auth_backends.AuthenticationBackend",
313337
]
314338

315-
SOCIALACCOUNT_PROVIDERS = {
316-
"github": {
317-
"SCOPE": [
318-
"read:user",
319-
"read:org",
320-
],
321-
"APPS": [
322-
{
323-
"client_id": get_secret("GH_CLIENT_ID"),
324-
"secret": get_secret("GH_SECRET"),
325-
"key": "",
326-
}
327-
],
328-
}
329-
}
330339

331340
REST_FRAMEWORK = {
332341
"DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"]

0 commit comments

Comments
 (0)