Skip to content

Commit 3ea998e

Browse files
committed
feat(settings): really plug settings object
1 parent 6e7e954 commit 3ea998e

10 files changed

+104
-36
lines changed

.github/workflows/code-quality.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ jobs:
5151
-e app_port=8000
5252
-v ${{github.workspace}}/tests:/app/tests
5353
-v ${{github.workspace}}/ecodev_core:/app/app
54+
-v ${{github.workspace}}/config:/app/app/assets
5455
-v ${{github.workspace}}:/app/
5556
-v ${{github.workspace}}/.coveragerc:/app/.coveragerc
5657
run: |

config/local.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
authentication:
3+
algorithm: null
4+
access_token_expire_minutes: null
5+
secret_key: null
6+
7+
database:
8+
db_port: null
9+
db_name: null
10+
db_host: null
11+
db_username: null
12+
db_password: null
13+
14+
elastic_search:
15+
port: null
16+
host: null
17+
user: null
18+
password: null
19+
index: null
20+
21+
backup:
22+
backup_username: null
23+
backup_password: null
24+
backup_url: null
25+
26+
smtp:
27+
email_smtp: null
28+
email_port: null
29+
email_sender: null
30+
email_password: null

ecodev_core/auth_configuration.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from pydantic_settings import BaseSettings
55
from pydantic_settings import SettingsConfigDict
66

7+
from ecodev_core.settings import SETTINGS
8+
79

810
class AuthenticationConfiguration(BaseSettings):
911
"""
@@ -16,3 +18,7 @@ class AuthenticationConfiguration(BaseSettings):
1618

1719

1820
AUTH = AuthenticationConfiguration()
21+
SETTINGS_AUTH = SETTINGS.authentication # type: ignore[attr-defined]
22+
SECRET_KEY = SETTINGS_AUTH.secret_key or AUTH.secret_key
23+
ALGO = SETTINGS_AUTH.algorithm or AUTH.algorithm
24+
EXPIRATION_LENGTH = SETTINGS_AUTH.access_token_expire_minutes or AUTH.access_token_expire_minutes

ecodev_core/authentication.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
from starlette.responses import RedirectResponse
2727

2828
from ecodev_core.app_user import AppUser
29-
from ecodev_core.auth_configuration import AUTH
29+
from ecodev_core.auth_configuration import ALGO
30+
from ecodev_core.auth_configuration import EXPIRATION_LENGTH
31+
from ecodev_core.auth_configuration import SECRET_KEY
3032
from ecodev_core.db_connection import engine
3133
from ecodev_core.logger import logger_get
3234
from ecodev_core.permissions import Permission
@@ -235,11 +237,11 @@ def _create_access_token(data: Dict, tfa_value: Optional[str] = None) -> str:
235237
Create an access token out of the passed data. Only called if credentials are valid
236238
"""
237239
to_encode = data.copy()
238-
expire = datetime.now(timezone.utc) + timedelta(minutes=AUTH.access_token_expire_minutes)
240+
expire = datetime.now(timezone.utc) + timedelta(minutes=EXPIRATION_LENGTH)
239241
to_encode['exp'] = expire
240242
if tfa_value:
241243
to_encode['tfa'] = _hash_password(tfa_value)
242-
return jwt.encode(to_encode, AUTH.secret_key, algorithm=AUTH.algorithm)
244+
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGO)
243245

244246

245247
def _verify_access_token(token: str,
@@ -249,7 +251,7 @@ def _verify_access_token(token: str,
249251
Retrieves the token data associated to the passed token if it contains valid credential info.
250252
"""
251253
try:
252-
payload = jwt.decode(token, AUTH.secret_key, algorithms=[AUTH.algorithm])
254+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGO])
253255
if tfa_check and (not tfa_value or not _check_password(tfa_value, payload.get('tfa'))):
254256
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=INVALID_TFA,
255257
headers={'WWW-Authenticate': 'Bearer'})

ecodev_core/backup.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from ecodev_core.db_connection import DB_URL
1717
from ecodev_core.logger import logger_get
18-
18+
from ecodev_core.settings import SETTINGS
1919

2020
log = logger_get(__name__)
2121

@@ -30,8 +30,11 @@ class BackUpSettings(BaseSettings):
3030
model_config = SettingsConfigDict(env_file='.env')
3131

3232

33-
BCK = BackUpSettings()
34-
BACKUP_URL = f'ftp://{BCK.backup_username}:{BCK.backup_password}@{BCK.backup_url}'
33+
BCK, SETTINGS_BCK = BackUpSettings(), SETTINGS.backup # type: ignore[attr-defined]
34+
_USER = SETTINGS_BCK.backup_username or BCK.backup_username
35+
_PASSWD = SETTINGS_BCK.backup_password or BCK.backup_password
36+
_URL = SETTINGS_BCK.backup_url or BCK.backup_url
37+
BACKUP_URL = f'ftp://{_USER}:{_PASSWD}@{_URL}'
3538

3639

3740
def backup(backed_folder: Path, nb_saves: int = 5, additional_id: str = 'default') -> None:

ecodev_core/db_connection.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from sqlmodel import SQLModel
1515

1616
from ecodev_core.logger import logger_get
17+
from ecodev_core.settings import SETTINGS
1718

1819
log = logger_get(__name__)
1920

@@ -30,9 +31,11 @@ class DbSettings(BaseSettings):
3031
model_config = SettingsConfigDict(env_file='.env')
3132

3233

33-
DB = DbSettings()
34-
_PASSWORD = quote(DB.db_password, safe='')
35-
DB_URL = f'postgresql://{DB.db_username}:{_PASSWORD}@{DB.db_host}:{DB.db_port}/{DB.db_name}'
34+
DB, SETTINGS_DB = DbSettings(), SETTINGS.database # type: ignore[attr-defined]
35+
_PASSWORD = quote(SETTINGS_DB.db_password or DB.db_password, safe='')
36+
_USER, _HOST = SETTINGS_DB.db_username or DB.db_username, SETTINGS_DB.db_host or DB.db_host
37+
_PORT, _NAME = SETTINGS_DB.db_port or DB.db_port, SETTINGS_DB.db_name or DB.db_name
38+
DB_URL = f'postgresql://{_USER}:{_PASSWORD}@{_HOST}:{_PORT}/{_NAME}'
3639
engine = create_engine(DB_URL)
3740

3841

ecodev_core/email_sender.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from pydantic_settings import BaseSettings
1212
from pydantic_settings import SettingsConfigDict
1313

14+
from ecodev_core.settings import SETTINGS
15+
1416

1517
class EmailAuth(BaseSettings):
1618
"""
@@ -19,10 +21,15 @@ class EmailAuth(BaseSettings):
1921
email_smtp: str = ''
2022
email_sender: str = ''
2123
email_password: str = ''
24+
email_port: int = 587
2225
model_config = SettingsConfigDict(env_file='.env')
2326

2427

25-
EMAIL_AUTH = EmailAuth()
28+
EMAIL_AUTH, EMAIL_SETTINGS = EmailAuth(), SETTINGS.smtp # type: ignore[attr-defined]
29+
_SENDER = EMAIL_SETTINGS.email_sender or EMAIL_AUTH.email_sender
30+
_SMTP = EMAIL_SETTINGS.email_smtp or EMAIL_AUTH.email_smtp
31+
_PASSWD = EMAIL_SETTINGS.email_password or EMAIL_AUTH.email_password
32+
_PORT = EMAIL_SETTINGS.email_port or EMAIL_AUTH.email_port
2633

2734

2835
def send_email(email: str, body: str, topic: str, images: dict[str, Path] | None = None) -> None:
@@ -36,7 +43,7 @@ def send_email(email: str, body: str, topic: str, images: dict[str, Path] | None
3643
- images: if any, the Dict of image tags:image paths to incorporate in the email
3744
"""
3845
em = MIMEMultipart('related')
39-
em['From'] = EMAIL_AUTH.email_sender
46+
em['From'] = _SENDER
4047
em['To'] = email
4148
em['Subject'] = topic
4249
em.attach(MIMEText(body, 'html'))
@@ -46,8 +53,8 @@ def send_email(email: str, body: str, topic: str, images: dict[str, Path] | None
4653
img.add_header('Content-ID', f'<{tag}>')
4754
em.attach(img)
4855

49-
with SMTP(EMAIL_AUTH.email_smtp, 587) as server:
56+
with SMTP(_SMTP, _PORT) as server:
5057
server.ehlo()
5158
server.starttls(context=create_default_context())
52-
server.login(EMAIL_AUTH.email_sender, EMAIL_AUTH.email_password)
53-
server.sendmail(EMAIL_AUTH.email_sender, email, em.as_string())
59+
server.login(_SENDER, _PASSWD)
60+
server.sendmail(_SENDER, email, em.as_string())

ecodev_core/es_connection.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pydantic_settings import SettingsConfigDict
1212

1313
from ecodev_core.logger import logger_get
14+
from ecodev_core.settings import SETTINGS
1415

1516
ES_CLIENT: Union[Elasticsearch, None] = None
1617
log = logger_get(__name__)
@@ -21,15 +22,18 @@ class ESAuth(BaseSettings):
2122
"""
2223
Simple ES authentication configuration class
2324
"""
24-
host: str
25-
user: str
26-
password: str
27-
port: int
28-
index: str
25+
host: str = ''
26+
user: str = ''
27+
password: str = ''
28+
port: int = 9200
29+
index: str = ''
2930
model_config = SettingsConfigDict(env_file='.env', env_prefix='ES_')
3031

3132

32-
ES_AUTH = ESAuth() # type: ignore
33+
ES_AUTH, ES_SETTINGS = ESAuth(), SETTINGS.elastic_search # type: ignore[attr-defined]
34+
_HOST, _PORT = ES_SETTINGS.host or ES_AUTH.host, ES_SETTINGS.port or ES_AUTH.port
35+
_USER, _PASSWD = ES_SETTINGS.user or ES_AUTH.user, ES_SETTINGS.password or ES_AUTH.password
36+
_INDEX = ES_SETTINGS.index or ES_AUTH.index
3337

3438

3539
def get_es_client():
@@ -39,8 +43,7 @@ def get_es_client():
3943
global ES_CLIENT
4044

4145
if ES_CLIENT is None:
42-
ES_CLIENT = Elasticsearch(f'http://{ES_AUTH.host}:{ES_AUTH.port}/',
43-
basic_auth=[ES_AUTH.user, ES_AUTH.password])
46+
ES_CLIENT = Elasticsearch(f'http://{_HOST}:{_PORT}/', basic_auth=[_USER, _PASSWD])
4447

4548
return ES_CLIENT
4649

@@ -51,11 +54,11 @@ def create_es_index(body: dict) -> None:
5154
"""
5255
client = get_es_client()
5356
try:
54-
client.indices.delete(index=ES_AUTH.index)
57+
client.indices.delete(index=_INDEX)
5558
except Exception:
5659
pass
57-
client.indices.create(index=ES_AUTH.index, body=body)
58-
log.info(f'index {ES_AUTH.index} created')
60+
client.indices.create(index=_INDEX, body=body)
61+
log.info(f'index {_INDEX} created')
5962

6063

6164
def insert_es_fields(operations: list[dict], batch_size: int = ES_BATCH_SIZE) -> None:
@@ -66,11 +69,11 @@ def insert_es_fields(operations: list[dict], batch_size: int = ES_BATCH_SIZE) ->
6669
batches = [list(operations)[i:i + batch_size] for i in range(0, len(operations), batch_size)]
6770
log.info('indexing fields')
6871
for batch in progressbar.progressbar(batches, redirect_stdout=False):
69-
helpers.bulk(client, batch, index=ES_AUTH.index)
72+
helpers.bulk(client, batch, index=_INDEX)
7073

7174

7275
def retrieve_es_fields(body: dict[str, Any]) -> list[dict]:
7376
"""
7477
Core call to the elasticsearch index
7578
"""
76-
return get_es_client().search(index=ES_AUTH.index, body=body)
79+
return get_es_client().search(index=_INDEX, body=body)

ecodev_core/settings.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Module defining a dynamic setting class
33
"""
4+
from contextlib import suppress
45
from pathlib import Path
56

67
from pydantic.v1.utils import deep_update
@@ -9,18 +10,24 @@
910

1011
from ecodev_core.deployment import Deployment
1112
from ecodev_core.list_utils import dict_to_class
13+
from ecodev_core.logger import logger_get
1214
from ecodev_core.read_write import load_yaml_file
1315

16+
log = logger_get(__name__)
17+
1418

1519
class DeploymentSetting(BaseSettings):
1620
"""
1721
Settings class used to load the deployment type from environment variables.
1822
"""
1923
environment: str = 'local'
24+
base_path: str = '/app/app/assets'
2025
model_config = SettingsConfigDict(env_file='.env')
2126

2227

23-
DEPLOYMENT = Deployment(DeploymentSetting().environment.lower())
28+
DEPLOYMENT_SETTINGS = DeploymentSetting()
29+
DEPLOYMENT = Deployment(DEPLOYMENT_SETTINGS.environment.lower())
30+
BASE_PATH = Path(DEPLOYMENT_SETTINGS.base_path)
2431

2532

2633
class Settings:
@@ -29,15 +36,21 @@ class Settings:
2936
this configuration with additional information coming from a secret file.
3037
"""
3138

32-
def __init__(self, base_path: Path = Path('/app'), deployment: Deployment = DEPLOYMENT):
39+
def __init__(self, base_path: Path = BASE_PATH, deployment: Deployment = DEPLOYMENT):
3340
"""
3441
Dynamically setting Settings attributes, doing so recursively. Attributes are loaded
3542
from config file, possibly overwriting some of this configuration with additional
3643
information coming from a secret file.
3744
"""
3845
self.deployment = deployment
39-
data = load_yaml_file(base_path / 'config' / f'{deployment.value}.yaml')
40-
if (secrets_file := base_path / 'secrets' / f'{deployment.value}.yaml').exists():
41-
data = deep_update(data, load_yaml_file(secrets_file))
42-
for k, v in dict_to_class(data).items():
43-
setattr(self, k, v)
46+
47+
with suppress(FileNotFoundError):
48+
log.info((base_path / 'config' / f'{deployment.value}.yaml').exists())
49+
data = load_yaml_file(base_path / 'config' / f'{deployment.value}.yaml')
50+
if (secrets_file := base_path / 'secrets' / f'{deployment.value}.yaml').exists():
51+
data = deep_update(data, load_yaml_file(secrets_file))
52+
for k, v in dict_to_class(data).items():
53+
setattr(self, k, v)
54+
55+
56+
SETTINGS = Settings()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "ecodev-core"
3-
version = "0.0.49"
3+
version = "0.0.50"
44
description = "Low level sqlmodel/fastapi/pydantic building blocks"
55
authors = ["Thomas Epelbaum <[email protected]>",
66
"Olivier Gabriel <[email protected]>",

0 commit comments

Comments
 (0)