Skip to content

Commit 6ef5931

Browse files
Brain256Brain256
andauthored
Update Backend Config Files (#663)
# Purpose Closes #656. Replaced the original database configuration setup in config.py with a new Pydantic database config class. # New Changes - Created a database configuration Pydantic class and added it to the backend config class - wrote additional unit tests # Testing Explain tests that you ran to verify code functionality. - [x] I have unit-tested this PR. Otherwise, explain why it cannot be unit-tested. - [x] I have included screenshots of the tests performed below. <img width="1654" height="252" alt="image" src="https://github.com/user-attachments/assets/fcda182b-2c25-47fe-98b5-24158bce67e1" /> # Outstanding Changes If there are non-critical changes (i.e. additional features) that can be made to this feature in the future, indicate them here. --------- Co-authored-by: Brain256 <brian@BrianYoga7.localdomain>
1 parent e3a8c43 commit 6ef5931

File tree

7 files changed

+115
-52
lines changed

7 files changed

+115
-52
lines changed

gs/backend/api/backend_setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from gs.backend.api.v1.mcc.endpoints.commands import commands_router
1313
from gs.backend.api.v1.mcc.endpoints.main_commands import main_commands_router
1414
from gs.backend.api.v1.mcc.endpoints.telemetry import telemetry_router
15-
from gs.backend.config.config import backend_config
15+
from gs.backend.config.config import settings
1616

1717

1818
def setup_routes(app: FastAPI) -> None:
@@ -38,7 +38,7 @@ def setup_middlewares(app: FastAPI) -> None:
3838
app.add_middleware(AuthMiddleware)
3939
app.add_middleware(
4040
LoggerMiddleware,
41-
excluded_endpoints=backend_config.logger_config.excluded_endpoints,
41+
excluded_endpoints=settings.logger.excluded_endpoints,
4242
)
4343

4444

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from fastapi import FastAPI
22
from fastapi.middleware.cors import CORSMiddleware
33

4-
from gs.backend.config.config import backend_config
4+
from gs.backend.config.config import settings
55

66

77
def add_cors_middleware(app: FastAPI) -> None:
@@ -12,8 +12,8 @@ def add_cors_middleware(app: FastAPI) -> None:
1212
"""
1313
app.add_middleware(
1414
CORSMiddleware,
15-
allow_origins=backend_config.cors_config.allow_origins,
16-
allow_credentials=backend_config.cors_config.allow_credentials,
17-
allow_methods=backend_config.cors_config.allow_methods,
18-
allow_headers=backend_config.cors_config.allow_headers,
15+
allow_origins=settings.cors.allow_origins,
16+
allow_credentials=settings.cors.allow_credentials,
17+
allow_methods=settings.cors.allow_methods,
18+
allow_headers=settings.cors.allow_headers,
1919
)

gs/backend/config/config.py

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
# TODO:(335) Improve loading the configuration
2-
from os import environ
3-
from typing import Final
42

5-
from dotenv import load_dotenv
6-
7-
from .cors_config import CORSConfig
8-
from .logger_config import LoggerConfig
9-
10-
load_dotenv()
3+
from gs.backend.config.cors_config import CORSConfig
4+
from gs.backend.config.database_config import DatabaseConfig
5+
from gs.backend.config.logger_config import LoggerConfig
116

127

138
class BackendConfiguration:
@@ -16,34 +11,9 @@ class BackendConfiguration:
1611
"""
1712

1813
def __init__(self) -> None:
19-
self.cors_config = CORSConfig()
20-
self.logger_config = LoggerConfig()
21-
22-
23-
backend_config = BackendConfiguration()
24-
25-
26-
def getenv(config: str) -> str:
27-
"""
28-
Validates whether or not database env values exist in .env. If not, throws a value error.
29-
30-
:param config: Variable in .env
31-
:type config: str
32-
:return: Value of variable in .env
33-
:rtype: str
34-
"""
35-
value = environ.get(config)
36-
if not value:
37-
raise ValueError(f"{config} is missing from .env.")
38-
return value
39-
14+
self.cors = CORSConfig()
15+
self.logger = LoggerConfig()
16+
self.db = DatabaseConfig()
4017

41-
GS_DATABASE_USER = getenv("GS_DATABASE_USER")
42-
GS_DATABASE_PASSWORD = getenv("GS_DATABASE_PASSWORD")
43-
GS_DATABASE_LOCATION = getenv("GS_DATABASE_LOCATION")
44-
GS_DATABASE_PORT = getenv("GS_DATABASE_PORT")
45-
GS_DATABASE_NAME = getenv("GS_DATABASE_NAME")
4618

47-
DATABASE_CONNECTION_STRING: Final[
48-
str
49-
] = f"postgresql+psycopg2://{GS_DATABASE_USER}:{GS_DATABASE_PASSWORD}@{GS_DATABASE_LOCATION}:{GS_DATABASE_PORT}/{GS_DATABASE_NAME}"
19+
settings = BackendConfiguration()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from pydantic import SecretStr
2+
from pydantic_settings import BaseSettings, SettingsConfigDict
3+
4+
5+
class DatabaseConfig(BaseSettings):
6+
"""
7+
Pydantic class for storing database configuration settings
8+
"""
9+
10+
model_config = SettingsConfigDict(env_prefix="GS_DATABASE_")
11+
12+
user: str
13+
password: SecretStr
14+
location: str
15+
port: int
16+
name: str
17+
18+
@property
19+
def connection_string(self) -> str:
20+
"""
21+
Returns the database connection string
22+
"""
23+
24+
pwd = self.password.get_secret_value()
25+
return f"postgresql://{self.user}:{pwd}@{self.location}:{self.port}/{self.name}"

gs/backend/data/database/engine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from sqlalchemy import Engine
22
from sqlmodel import Session, create_engine, text
33

4-
from gs.backend.config.config import DATABASE_CONNECTION_STRING
4+
from gs.backend.config.config import settings
55
from gs.backend.data.tables.aro_user_tables import ARO_USER_SCHEMA_NAME
66
from gs.backend.data.tables.main_tables import MAIN_SCHEMA_NAME
77
from gs.backend.data.tables.transactional_tables import TRANSACTIONAL_SCHEMA_NAME
@@ -13,7 +13,7 @@ def get_db_engine() -> Engine:
1313
1414
:return: engine
1515
"""
16-
return create_engine(DATABASE_CONNECTION_STRING)
16+
return create_engine(settings.db.connection_string)
1717

1818

1919
def get_db_session() -> Session:

python_test/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
import subprocess
33
from datetime import datetime
44

5+
os.environ.setdefault("GS_DATABASE_USER", "testuser")
6+
os.environ.setdefault("GS_DATABASE_PASSWORD", "testpassword")
7+
os.environ.setdefault("GS_DATABASE_LOCATION", "localhost")
8+
os.environ.setdefault("GS_DATABASE_PORT", "5432")
9+
os.environ.setdefault("GS_DATABASE_NAME", "testdb")
10+
511
import pytest
612
from gs.backend.data.database.engine import setup_database
713
from gs.backend.data.tables.transactional_tables import CommsSession

python_test/test_config_manager.py

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import importlib
22

3-
from gs.backend.config import config
3+
import pytest
4+
from gs.backend.config.config import BackendConfiguration, settings
45
from gs.backend.config.cors_config import CORSConfig
6+
from gs.backend.config.database_config import DatabaseConfig
57
from gs.backend.config.logger_config import LoggerConfig
8+
from pydantic import ValidationError
69

710

811
def test_logger_config_default():
912
cfg = LoggerConfig()
13+
1014
assert cfg.excluded_endpoints == []
1115

1216

@@ -21,9 +25,67 @@ def test_cors_config_default():
2125

2226
def test_backend_configuration_from_env(monkeypatch):
2327
monkeypatch.setenv("LOGGER_EXCLUDED_ENDPOINTS", '["/test"]')
24-
monkeypatch.setenv("CORS_ALLOW_ORIGINS", '["http://test.com"]')
2528

26-
importlib.reload(config)
27-
cfg = config.backend_config
28-
assert "/test" in cfg.logger_config.excluded_endpoints
29-
assert "http://test.com" in cfg.cors_config.allow_origins
29+
monkeypatch.setenv("CORS_ALLOW_ORIGINS", '["http://localhost:5173"]')
30+
monkeypatch.setenv("CORS_ALLOW_CREDENTIALS", "True")
31+
monkeypatch.setenv("CORS_ALLOW_METHODS", '["*"]')
32+
monkeypatch.setenv("CORS_ALLOW_HEADERS", '["*"]')
33+
34+
monkeypatch.setenv("GS_DATABASE_USER", "testuser")
35+
monkeypatch.setenv("GS_DATABASE_PASSWORD", "testpassword")
36+
monkeypatch.setenv("GS_DATABASE_LOCATION", "localhost")
37+
monkeypatch.setenv("GS_DATABASE_PORT", "5432")
38+
monkeypatch.setenv("GS_DATABASE_NAME", "testdb")
39+
40+
cfg = BackendConfiguration()
41+
42+
assert "/test" in cfg.logger.excluded_endpoints
43+
assert "http://localhost:5173" in cfg.cors.allow_origins
44+
assert cfg.db.user == "testuser"
45+
assert cfg.db.password.get_secret_value() == "testpassword"
46+
assert cfg.db.location == "localhost"
47+
assert cfg.db.port == 5432
48+
assert cfg.db.name == "testdb"
49+
50+
51+
def test_database_connection_string(monkeypatch):
52+
monkeypatch.setenv("GS_DATABASE_USER", "testuser")
53+
monkeypatch.setenv("GS_DATABASE_PASSWORD", "testpassword")
54+
monkeypatch.setenv("GS_DATABASE_LOCATION", "localhost")
55+
monkeypatch.setenv("GS_DATABASE_PORT", "5432")
56+
monkeypatch.setenv("GS_DATABASE_NAME", "testdb")
57+
58+
cfg = BackendConfiguration()
59+
db = cfg.db
60+
61+
assert db.password.get_secret_value() == "testpassword"
62+
expected_url = "postgresql://testuser:testpassword@localhost:5432/testdb"
63+
assert db.connection_string == expected_url
64+
65+
66+
def test_database_missing_env(monkeypatch):
67+
monkeypatch.setenv("GS_DATABASE_USER", "testuser")
68+
monkeypatch.setenv("GS_DATABASE_PASSWORD", "testpassword")
69+
monkeypatch.setenv("GS_DATABASE_LOCATION", "localhost")
70+
monkeypatch.setenv("GS_DATABASE_PORT", "5432")
71+
monkeypatch.setenv("GS_DATABASE_NAME", "testdb")
72+
73+
monkeypatch.delenv("GS_DATABASE_PASSWORD")
74+
75+
with pytest.raises(ValidationError):
76+
DatabaseConfig()
77+
78+
79+
def test_invalid_env(monkeypatch):
80+
monkeypatch.setenv("GS_DATABASE_PORT", "test")
81+
monkeypatch.setenv("CORS_ALLOW_CREDENTIALS", "3")
82+
monkeypatch.setenv("LOGGER_EXCLUDED_ENDPOINTS", "3")
83+
84+
with pytest.raises(ValidationError):
85+
DatabaseConfig()
86+
87+
with pytest.raises(ValidationError):
88+
CORSConfig()
89+
90+
with pytest.raises(ValidationError):
91+
LoggerConfig()

0 commit comments

Comments
 (0)