Skip to content

Commit e46cca5

Browse files
committed
Refactor Settings creation
1 parent a0d9104 commit e46cca5

File tree

3 files changed

+47
-77
lines changed

3 files changed

+47
-77
lines changed

tiled/authn_database/connection_pool.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from fastapi import Depends
22
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
33

4-
from ..server.settings import get_settings
4+
from ..server.settings import Settings, get_settings
55
from ..utils import ensure_specified_sql_driver
66

77
# A given process probably only has one of these at a time, but we
@@ -31,9 +31,9 @@ async def close_database_connection_pool(database_settings):
3131
await engine.dispose()
3232

3333

34-
async def get_database_engine(settings=Depends(get_settings)):
34+
async def get_database_engine(settings: Settings = Depends(get_settings)):
3535
# Special case for single-user mode
36-
if settings.database_uri is None:
36+
if settings.database_settings.uri is None:
3737
return None
3838
try:
3939
return _connection_pools[settings.database_settings]

tiled/server/app.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
from .compression import CompressionMiddleware
5252
from .dependencies import get_root_tree
5353
from .router import get_router
54-
from .settings import get_settings
54+
from .settings import Settings, get_settings
5555
from .utils import (
5656
API_KEY_COOKIE_NAME,
5757
CSRF_COOKIE_NAME,
@@ -461,29 +461,31 @@ def override_get_settings():
461461
if server_settings.get(item) is not None:
462462
setattr(settings, item, server_settings[item])
463463
database = server_settings.get("database", {})
464-
if database.get("uri"):
465-
settings.database_uri = database["uri"]
466-
if database.get("pool_size"):
467-
settings.database_pool_size = database["pool_size"]
468-
if database.get("pool_pre_ping"):
469-
settings.database_pool_pre_ping = database["pool_pre_ping"]
470-
if database.get("max_overflow"):
471-
settings.database_max_overflow = database["max_overflow"]
472-
if database.get("init_if_not_exists"):
473-
settings.database_init_if_not_exists = database["init_if_not_exists"]
464+
if uri := database.get("uri"):
465+
settings.database_settings.uri = uri
466+
if pool_size := database.get("pool_size"):
467+
settings.database_settings.pool_size = pool_size
468+
if pool_pre_ping := database.get("pool_pre_ping"):
469+
settings.database_settings.pool_pre_ping = pool_pre_ping
470+
if max_overflow := database.get("max_overflow"):
471+
settings.database_settings.max_overflow = max_overflow
472+
if init_if_not_exists := database.get("init_if_not_exists"):
473+
settings.database_init_if_not_exists = init_if_not_exists
474474
if authentication.get("providers"):
475475
# If we support authentication providers, we need a database, so if one is
476476
# not set, use a SQLite database in memory. Horizontally scaled deployments
477477
# must specify a persistent database.
478-
settings.database_uri = settings.database_uri or "sqlite://"
478+
settings.database_settings.uri = (
479+
settings.database_settings.uri or "sqlite://"
480+
)
479481
return settings
480482

481483
async def startup_event():
482484
from .. import __version__
483485

484486
logger.info(f"Tiled version {__version__}")
485487
# Validate the single-user API key.
486-
settings = app.dependency_overrides[get_settings]()
488+
settings: Settings = app.dependency_overrides[get_settings]()
487489
single_user_api_key = settings.single_user_api_key
488490
API_KEY_MSG = """
489491
Here are two ways to generate a good API key:
@@ -535,7 +537,7 @@ async def startup_event():
535537
# client.context.app.state.root_tree
536538
app.state.root_tree = app.dependency_overrides[get_root_tree]()
537539

538-
if settings.database_uri is not None:
540+
if settings.database_settings.uri is not None:
539541
from sqlalchemy.ext.asyncio import AsyncSession
540542

541543
from ..alembic_utils import (
@@ -660,8 +662,8 @@ async def shutdown_event():
660662
for task in tasks.get("shutdown", []):
661663
await task()
662664

663-
settings = app.dependency_overrides[get_settings]()
664-
if settings.database_uri is not None:
665+
settings: Settings = app.dependency_overrides[get_settings]()
666+
if settings.database_settings.uri is not None:
665667
from ..authn_database.connection_pool import close_database_connection_pool
666668

667669
for task in app.state.tasks:

tiled/server/settings.py

Lines changed: 26 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,50 @@
1-
import collections
21
import os
32
import secrets
43
from datetime import timedelta
54
from functools import cache
65
from typing import Any, List, Optional
76

8-
from pydantic_settings import BaseSettings
7+
from pydantic import Field
8+
from pydantic.dataclasses import dataclass
9+
from pydantic_settings import BaseSettings, SettingsConfigDict
910

10-
DatabaseSettings = collections.namedtuple(
11-
"DatabaseSettings", "uri pool_size pool_pre_ping max_overflow"
12-
)
11+
12+
# hashable cache key for use in tiled.authn_database.connection_pool
13+
@dataclass(unsafe_hash=True)
14+
class DatabaseSettings:
15+
uri: Optional[str] = None
16+
pool_size: int = 5
17+
pool_pre_ping: bool = True
18+
max_overflow: int = 5
1319

1420

1521
class Settings(BaseSettings):
1622
tree: Any = None
17-
allow_anonymous_access: bool = bool(
18-
int(os.getenv("TILED_ALLOW_ANONYMOUS_ACCESS", False))
19-
)
20-
allow_origins: List[str] = [
21-
item for item in os.getenv("TILED_ALLOW_ORIGINS", "").split() if item
22-
]
23+
allow_anonymous_access: bool = False
24+
allow_origins: List[str] = Field(default_factory=list)
2325
authenticator: Any = None
2426
# These 'single user' settings are only applicable if authenticator is None.
25-
single_user_api_key: str = os.getenv(
26-
"TILED_SINGLE_USER_API_KEY", secrets.token_hex(32)
27-
)
28-
single_user_api_key_generated: bool = not (
29-
"TILED_SINGLE_USER_API_KEY" in os.environ
30-
)
27+
single_user_api_key: str = secrets.token_hex(32)
28+
single_user_api_key_generated: bool = "TILED_SINGLE_USER_API_KEY" not in os.environ
3129
# The TILED_SERVER_SECRET_KEYS may be a single key or a ;-separated list of
3230
# keys to support key rotation. The first key will be used for encryption. Each
3331
# key will be tried in turn for decryption.
34-
secret_keys: List[str] = os.getenv(
35-
"TILED_SERVER_SECRET_KEYS", secrets.token_hex(32)
36-
).split(";")
37-
access_token_max_age: timedelta = timedelta(
38-
seconds=int(os.getenv("TILED_ACCESS_TOKEN_MAX_AGE", 15 * 60)) # 15 minutes
39-
)
40-
refresh_token_max_age: timedelta = timedelta(
41-
seconds=int(
42-
os.getenv("TILED_REFRESH_TOKEN_MAX_AGE", 7 * 24 * 60 * 60)
43-
) # 7 days
44-
)
45-
session_max_age: Optional[timedelta] = timedelta(
46-
seconds=int(os.getenv("TILED_SESSION_MAX_AGE", 365 * 24 * 60 * 60)) # 365 days
47-
)
32+
secret_keys: List[str] = [secrets.token_hex(32)]
33+
access_token_max_age: timedelta = 15 * 60 # 15 minutes
34+
refresh_token_max_age: timedelta = 7 * 24 * 60 * 60 # 7 days
35+
session_max_age: timedelta = 365 * 24 * 60 * 60 # 365 days
4836
# Put a fairly low limit on the maximum size of one chunk, keeping in mind
4937
# that data should generally be chunked. When we implement async responses,
5038
# we can raise this global limit.
51-
response_bytesize_limit: int = int(
52-
os.getenv("TILED_RESPONSE_BYTESIZE_LIMIT", 300_000_000)
53-
) # 300 MB
54-
reject_undeclared_specs: bool = bool(
55-
int(os.getenv("TILED_REJECT_UNDECLARED_SPECS", 0))
56-
)
57-
database_uri: Optional[str] = os.getenv("TILED_DATABASE_URI")
58-
database_init_if_not_exists: bool = int(
59-
os.getenv("TILED_DATABASE_INIT_IF_NOT_EXISTS", False)
60-
)
61-
database_pool_size: Optional[int] = int(os.getenv("TILED_DATABASE_POOL_SIZE", 5))
62-
database_pool_pre_ping: Optional[bool] = bool(
63-
int(os.getenv("TILED_DATABASE_POOL_PRE_PING", 1))
64-
)
65-
database_max_overflow: Optional[int] = int(
66-
os.getenv("TILED_DATABASE_MAX_OVERFLOW", 5)
67-
)
39+
response_bytesize_limit: int = 300_000_000 # 300 MB
40+
reject_undeclared_specs: bool = False
41+
database_settings: DatabaseSettings = Field(DatabaseSettings(), alias="database")
42+
database_init_if_not_exists: bool = False
6843
expose_raw_assets: bool = True
6944

70-
@property
71-
def database_settings(self):
72-
# The point of this alias is to return a hashable cache key for use in
73-
# the module tiled.authn_database.connection_pool.
74-
return DatabaseSettings(
75-
uri=self.database_uri,
76-
pool_size=self.database_pool_size,
77-
pool_pre_ping=self.database_pool_pre_ping,
78-
max_overflow=self.database_max_overflow,
79-
)
45+
model_config = SettingsConfigDict(
46+
env_prefix="TILED_", nested_model_default_partial_update=True
47+
)
8048

8149

8250
@cache

0 commit comments

Comments
 (0)