|
1 | | -import collections |
2 | 1 | import os |
3 | 2 | import secrets |
4 | 3 | from datetime import timedelta |
5 | 4 | from functools import cache |
6 | 5 | from typing import Any, List, Optional |
7 | 6 |
|
8 | | -from pydantic_settings import BaseSettings |
| 7 | +from pydantic import Field |
| 8 | +from pydantic.dataclasses import dataclass |
| 9 | +from pydantic_settings import BaseSettings, SettingsConfigDict |
9 | 10 |
|
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 |
13 | 19 |
|
14 | 20 |
|
15 | 21 | class Settings(BaseSettings): |
| 22 | + """A BaseSettings object defining configuration for the tiled instance. |
| 23 | + For loading variables from the environment, prefix with TILED_ and see: |
| 24 | + https://docs.pydantic.dev/latest/concepts/pydantic_settings/#parsing-environment-variable-values |
| 25 | + """ |
| 26 | + |
16 | 27 | 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 | | - ] |
| 28 | + allow_anonymous_access: bool = False |
| 29 | + allow_origins: List[str] = Field(default_factory=list) |
23 | 30 | authenticator: Any = None |
24 | 31 | # 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 | | - ) |
31 | | - # The TILED_SERVER_SECRET_KEYS may be a single key or a ;-separated list of |
32 | | - # keys to support key rotation. The first key will be used for encryption. Each |
33 | | - # 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 | + single_user_api_key: str = secrets.token_hex(32) |
| 33 | + single_user_api_key_generated: bool = "TILED_SINGLE_USER_API_KEY" not in os.environ |
| 34 | + # The first key will be used for encryption. Each key will be tried in turn for decryption. |
| 35 | + secret_keys: List[str] = [secrets.token_hex(32)] |
| 36 | + access_token_max_age: timedelta = 15 * 60 # 15 minutes |
| 37 | + refresh_token_max_age: timedelta = 7 * 24 * 60 * 60 # 7 days |
| 38 | + session_max_age: timedelta = 365 * 24 * 60 * 60 # 365 days |
48 | 39 | # Put a fairly low limit on the maximum size of one chunk, keeping in mind |
49 | 40 | # that data should generally be chunked. When we implement async responses, |
50 | 41 | # 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 | | - ) |
| 42 | + response_bytesize_limit: int = 300_000_000 # 300 MB |
| 43 | + reject_undeclared_specs: bool = False |
| 44 | + # "env_prefix does not apply to fields with alias" |
| 45 | + # https://docs.pydantic.dev/latest/concepts/pydantic_settings/#environment-variable-names |
| 46 | + database_settings: DatabaseSettings = Field( |
| 47 | + DatabaseSettings(), validation_alias="TILED_DATABASE" |
| 48 | + ) |
| 49 | + database_init_if_not_exists: bool = False |
68 | 50 | expose_raw_assets: bool = True |
69 | 51 |
|
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 | | - ) |
| 52 | + model_config = SettingsConfigDict( |
| 53 | + env_prefix="TILED_", |
| 54 | + nested_model_default_partial_update=True, |
| 55 | + env_nested_delimiter="_", |
| 56 | + ) |
80 | 57 |
|
81 | 58 |
|
82 | 59 | @cache |
|
0 commit comments