Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#186: Split database connection into testing and production #187

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Backend/app/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from app.common.app_schema import AppConfig, AppInfo
from app.common.PropertiesManager import PropertiesManager
from app.database.DatabaseConnection import DatabaseConnection
from app.database.database_connection_provider import init_database_connection
from app.logging.logging_constants import LOGGING_MAIN
from app.logging.logging_schema import SpotifyElectronLogger
from app.middleware.cors_middleware_config import (
Expand Down Expand Up @@ -49,7 +49,7 @@ async def lifespan_handler(app: FastAPI):
"""
main_logger.info("Spotify Electron Backend Started")

DatabaseConnection()
init_database_connection(PropertiesManager.get_enviroment())

app.include_router(playlist_controller.router)
app.include_router(song_controller.router)
Expand Down
10 changes: 6 additions & 4 deletions Backend/app/auth/auth_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
UserUnauthorizedException,
VerifyPasswordException,
)
from app.common.app_schema import AppEnviroment
from app.common.PropertiesManager import PropertiesManager
from app.common.set_up_constants import SECRET_KEY_SIGN_ENV_NAME
from app.exceptions.base_exceptions_schema import BadParameterException
from app.logging.logging_constants import LOGGING_AUTH_SERVICE
from app.logging.logging_schema import SpotifyElectronLogger
Expand Down Expand Up @@ -73,7 +73,7 @@ def create_access_token(data: dict[str, str], expires_delta: timedelta | None =
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(
to_encode,
getattr(PropertiesManager, SECRET_KEY_SIGN_ENV_NAME),
getattr(PropertiesManager, AppEnviroment.SECRET_KEY_SIGN_ENV_NAME),
algorithm=ALGORITHM,
)
except Exception as exception:
Expand Down Expand Up @@ -105,7 +105,7 @@ def get_jwt_token_data(
try:
payload = jwt.decode(
token_raw_data, # type: ignore
getattr(PropertiesManager, SECRET_KEY_SIGN_ENV_NAME),
getattr(PropertiesManager, AppEnviroment.SECRET_KEY_SIGN_ENV_NAME),
algorithms=[ALGORITHM],
)
username = payload.get("access_token")
Expand Down Expand Up @@ -290,7 +290,9 @@ def validate_jwt(token: str) -> None:
"""
try:
decoded_token = jwt.decode(
token, getattr(PropertiesManager, SECRET_KEY_SIGN_ENV_NAME), ALGORITHM
token,
getattr(PropertiesManager, AppEnviroment.SECRET_KEY_SIGN_ENV_NAME),
ALGORITHM,
)
validate_token_is_expired(decoded_token)

Expand Down
49 changes: 27 additions & 22 deletions Backend/app/common/PropertiesManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,8 @@

from app.common.app_schema import (
AppConfig,
)
from app.common.set_up_constants import (
ARCHITECTURE_ENV_NAME,
DEFAULT_ARCHITECTURE,
DEV,
ENV_VALUE_ENV_NAME,
MONGO_URI_ENV_NAME,
PROD,
SECRET_KEY_SIGN_ENV_NAME,
SERVERLESS_FUNCTION_URL_ENV_NAME,
TEST,
AppEnviroment,
AppEnvironmentMode,
)
from app.logging.logging_constants import LOGGING_PROPERTIES_MANAGER
from app.logging.logging_schema import SpotifyElectronLogger
Expand All @@ -39,10 +30,10 @@ def __init__(self) -> None:
self.current_directory = os.getcwd()
self.config_sections = [AppConfig.APP_INI_SECTION]
self.env_variables = [
MONGO_URI_ENV_NAME,
SECRET_KEY_SIGN_ENV_NAME,
SERVERLESS_FUNCTION_URL_ENV_NAME,
ENV_VALUE_ENV_NAME,
AppEnviroment.MONGO_URI_ENV_NAME,
AppEnviroment.SECRET_KEY_SIGN_ENV_NAME,
AppEnviroment.SERVERLESS_FUNCTION_URL_ENV_NAME,
AppEnviroment.ENV_VALUE_ENV_NAME,
]
self._load_env_variables(self.env_variables)
self._load_architecture()
Expand Down Expand Up @@ -87,13 +78,13 @@ def _load_architecture(self):
"""Loads the current architecture from enviroment and stores it as an\
attribute, if none is provided DEFAULT_ARCHITECTURE will be selected
"""
architecture_type = os.getenv(ARCHITECTURE_ENV_NAME)
architecture_type = os.getenv(AppEnviroment.ARCHITECTURE_ENV_NAME)
if not architecture_type:
architecture_type = DEFAULT_ARCHITECTURE
architecture_type = AppEnviroment.DEFAULT_ARCHITECTURE
properties_manager_logger.warning(
f"No architecture type selected, using {DEFAULT_ARCHITECTURE}"
f"No architecture type selected, using {AppEnviroment.DEFAULT_ARCHITECTURE}"
)
self.__setattr__(ARCHITECTURE_ENV_NAME, architecture_type)
self.__setattr__(AppEnviroment.ARCHITECTURE_ENV_NAME, architecture_type)
properties_manager_logger.info(f"Architecture selected : {architecture_type}")

def _load_env_variables(self, env_names: list[str]):
Expand All @@ -115,6 +106,14 @@ def _load_env_variables(self, env_names: list[str]):
self.__setattr__(env_name, env_variable_value)
properties_manager_logger.info(f"Enviroment variables loaded : {env_names}")

def get_enviroment(self) -> AppEnvironmentMode:
"""Get current enviroment

Returns:
Enviroment: the current selected enviroment
"""
return self.__getattribute__(AppEnviroment.ENV_VALUE_ENV_NAME)

def is_production_enviroment(self) -> bool:
"""Checks if the enviroment is production

Expand All @@ -123,7 +122,9 @@ def is_production_enviroment(self) -> bool:
bool: Returns if its production enviroment

"""
return self.__getattribute__(ENV_VALUE_ENV_NAME) == PROD
return (
self.__getattribute__(AppEnviroment.ENV_VALUE_ENV_NAME) == AppEnvironmentMode.PROD
)

def is_development_enviroment(self) -> bool:
"""Checks if the enviroment is development
Expand All @@ -133,7 +134,9 @@ def is_development_enviroment(self) -> bool:
bool: Returns if its development enviroment

"""
return self.__getattribute__(ENV_VALUE_ENV_NAME) == DEV
return (
self.__getattribute__(AppEnviroment.ENV_VALUE_ENV_NAME) == AppEnvironmentMode.DEV
)

def is_testing_enviroment(self) -> bool:
"""Checks if the enviroment is testing
Expand All @@ -143,7 +146,9 @@ def is_testing_enviroment(self) -> bool:
bool: Returns if its testing enviroment

"""
return self.__getattribute__(ENV_VALUE_ENV_NAME) == TEST
return (
self.__getattribute__(AppEnviroment.ENV_VALUE_ENV_NAME) == AppEnvironmentMode.TEST
)

def is_log_file_provided(self) -> bool:
"""Checks if theres a valid log file provided
Expand Down
51 changes: 40 additions & 11 deletions Backend/app/common/app_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@
App config schema
"""

from enum import StrEnum


class AppInfo:
"""App info constants"""

TITLE = "Spotify Electron Backend API"
DESCRIPTION = "API created with Python FastAPI to serve\
as backend for Spotify Electron music streaming Desktop App"
VERSION = "1.0.0"
CONTACT_NAME = "Antonio Martínez Fernández"
CONTACT_URL = "https://github.com/AntonioMrtz"
CONTACT_EMAIL = "antoniomartinezfernandez17@gmail.com"
LICENSE_INFO_NAME = "Attribution-NonCommercial-ShareAlike 4.0 International"
LICENSE_INFO_URL = "https://creativecommons.org/licenses/by-nc-sa/4.0/deed.es"


class AppConfig:
"""App config constants"""
Expand All @@ -21,15 +37,28 @@ class AppConfig:
PORT_INI_KEY = "port"


class AppInfo:
"""App info constants"""
class AppEnvironmentMode(StrEnum):
"""App enviroment mode constants"""

TITLE = "Spotify Electron Backend API"
DESCRIPTION = "API created with Python FastAPI to serve\
as backend for Spotify Electron music streaming Desktop App"
VERSION = "1.0.0"
CONTACT_NAME = "Antonio Martínez Fernández"
CONTACT_URL = "https://github.com/AntonioMrtz"
CONTACT_EMAIL = "antoniomartinezfernandez17@gmail.com"
LICENSE_INFO_NAME = "Attribution-NonCommercial-ShareAlike 4.0 International"
LICENSE_INFO_URL = "https://creativecommons.org/licenses/by-nc-sa/4.0/deed.es"
PROD = "PROD"
DEV = "DEV"
TEST = "TEST"


class AppArchitecture:
"""App architecture constants"""

ARCH_STREAMING_SERVERLESS_FUNCTION = "STREAMING_SERVERLESS_FUNCTION"
ARCH_BLOB = "BLOB"


class AppEnviroment:
"""App enviroment constants"""

ARCHITECTURE_ENV_NAME = "ARCH"
DEFAULT_ARCHITECTURE = AppArchitecture.ARCH_BLOB

SECRET_KEY_SIGN_ENV_NAME = "SECRET_KEY_SIGN"
MONGO_URI_ENV_NAME = "MONGO_URI"
SERVERLESS_FUNCTION_URL_ENV_NAME = "SERVERLESS_FUNCTION_URL"
ENV_VALUE_ENV_NAME = "ENV_VALUE"
18 changes: 0 additions & 18 deletions Backend/app/common/set_up_constants.py

This file was deleted.

27 changes: 27 additions & 0 deletions Backend/app/database/DatabaseProductionConnection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Database connection for production"""

from typing import Any

from mongomock import MongoClient

from app.database.database_schema import BaseDatabaseConnection


class DatabaseProductionConnection(BaseDatabaseConnection):
"""Database connection for production"""

def _get_mongo_client(self) -> Any:
"""Get Mongo client class

Returns:
Any: the Mongo client class
"""
return MongoClient

def _get_collection_name_prefix(self) -> str:
"""Returns prod prefix for collections

Returns:
str: the prod prefix for collections
"""
return ""
29 changes: 29 additions & 0 deletions Backend/app/database/DatabaseTestingConnection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""In-memory Database connection for testing"""

from mongomock.gridfs import enable_gridfs_integration
from mongomock.mongo_client import MongoClient as MongoClientMock

from app.database.database_schema import BaseDatabaseConnection


class DatabaseTestingConnection(BaseDatabaseConnection):
"""Database connection for testing. Uses an in-memory database"""

TESTING_COLLECTION_NAME_PREFIX = "test."

def _get_mongo_client(self):
"""Get mock Mongo client class

Returns:
Any: the mock Mongo client class
"""
enable_gridfs_integration()
return MongoClientMock

def _get_collection_name_prefix(self) -> str:
"""Returns prefix for testing

Returns:
str: the testing prefix for collections
"""
return self.TESTING_COLLECTION_NAME_PREFIX
42 changes: 42 additions & 0 deletions Backend/app/database/database_connection_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Database connection provider"""

from app.common.app_schema import AppEnvironmentMode
from app.database.database_schema import BaseDatabaseConnection
from app.database.DatabaseProductionConnection import DatabaseProductionConnection
from app.database.DatabaseTestingConnection import DatabaseTestingConnection

database_connection_mapping = {
AppEnvironmentMode.PROD: DatabaseProductionConnection,
AppEnvironmentMode.DEV: DatabaseProductionConnection,
AppEnvironmentMode.TEST: DatabaseTestingConnection,
}


def init_database_connection(current_enviroment: AppEnvironmentMode) -> None:
"""Initializes the database connection and loads its unique instance\
based on current enviroment value

Args:
current_enviroment (Enviroment): the current enviroment value
"""
global database_connection
database_connection_class = database_connection_mapping.get(
current_enviroment, DatabaseProductionConnection
)
database_connection_class()
DatabaseConnection.set_database_connection(database_connection_class)


class DatabaseConnection:
"""Database connection"""

connection_instance: BaseDatabaseConnection = None # type: ignore

@classmethod
def set_database_connection(cls, connection_instance: BaseDatabaseConnection):
"""Set database connection class attribute

Args:
connection_instance (BaseDatabaseConnection): the database connection instance
"""
cls.connection_instance = connection_instance
Loading