Skip to content

Commit

Permalink
refactor: Integrated config parser to read .ini
Browse files Browse the repository at this point in the history
removed unused settings and config_manager
modified config_service to be the class that handles configuration
modified all instances of config_manager to use config_service
  • Loading branch information
Killg0d committed Dec 27, 2024
1 parent 6fefb15 commit d9bc842
Show file tree
Hide file tree
Showing 34 changed files with 202 additions and 266 deletions.
23 changes: 23 additions & 0 deletions config/custom-environment-variables.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[MAILER]
DEFAULT_EMAIL =
DEFAULT_EMAIL_NAME =
FORGOT_PASSWORD_MAIL_TEMPLATE_ID =

[INSPECTLET]
KEY =

[PAPERTRAIL]
HOST =
PORT =

[SENDGRID]
API_KEY =

[OTP]
DEFAULT_PHONE_NUMBER =
DEFAULT_OTP =

[TWILIO]
ACCOUNT_SID =
AUTH_TOKEN =
MESSAGING_SERVICE_SID =
14 changes: 14 additions & 0 deletions config/default.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[SERVER_CONFIG]
SERVER_PORT = 8080
WEB_APP_HOST = http://localhost:3000
MONGODB_CONNECTION_CACHING = True

[LOGGER]
LOGGER_TRANSPORTS = console


[ACCOUNTS]
TOKEN_SIGNING_KEY = JWT_TOKEN
TOKEN_EXPIRY_DAYS = 1
TOKEN_EXPIRES_IN_SECONDS = 3600

9 changes: 9 additions & 0 deletions config/development.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[LOGGER]
LOGGER_TRANSPORTS = console

[MONGODB]
URI = mongodb://localhost:27017/frm-boilerplate-dev

[SMS]
SMS_ENABLED = False

8 changes: 8 additions & 0 deletions config/docker-dev.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[LOGGER]
LOGGER_TRANSPORTS = console

[MONGODB]
URI = mongodb://db:27017/frm-boilerplate-dev

[SMS]
SMS_ENABLED = False
11 changes: 11 additions & 0 deletions config/docker-test.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

[MONGODB]
URI = mongodb://db:27017/frm-boilerplate-test

[MAILER]
DEFAULT_EMAIL = DEFAULT_EMAIL
DEFAULT_EMAIL_NAME = DEFAULT_EMAIL_NAME
FORGOT_PASSWORD_MAIL_TEMPLATE_ID = FORGOT_PASSWORD_MAIL_TEMPLATE_ID

[SMS]
SMS_ENABLED = False
5 changes: 5 additions & 0 deletions config/preview.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[LOGGER]
LOGGER_TRANSPORTS = console,papertrail

[SMS]
SMS_ENABLED = True
5 changes: 5 additions & 0 deletions config/production.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[LOGGER]
LOGGER_TRANSPORTS = console,papertrail

[SMS]
SMS_ENABLED = True
11 changes: 11 additions & 0 deletions config/testing.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[MONGODB]
URI = mongodb://localhost:27017/frm-boilerplate-test

[MAILER]
DEFAULT_EMAIL = DEFAULT_EMAIL
DEFAULT_EMAIL_NAME = DEFAULT_EMAIL_NAME
FORGOT_PASSWORD_MAIL_TEMPLATE_ID = FORGOT_PASSWORD_MAIL_TEMPLATE_ID

[SMS]
SMS_ENABLED = False

6 changes: 3 additions & 3 deletions src/apps/backend/modules/access_token/access_token_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ def create_access_token_by_phone_number(*, params: OTPBasedAuthAccessTokenReques

@staticmethod
def __generate_access_token(*, account: Account) -> AccessToken:
jwt_signing_key = ConfigService.get_token_signing_key()
jwt_expiry = timedelta(days=ConfigService.get_token_expiry_days())
jwt_signing_key = ConfigService.get_value(key="TOKEN_SIGNING_KEY", section="ACCOUNTS")
jwt_expiry = timedelta(days=int(ConfigService.get_value(key="TOKEN_EXPIRY_DAYS", section="ACCOUNTS")))
payload = {"account_id": account.id, "exp": (datetime.now() + jwt_expiry).timestamp()}
jwt_token = jwt.encode(payload, jwt_signing_key, algorithm="HS256")
access_token = AccessToken(token=jwt_token, account_id=account.id, expires_at=str(payload["exp"]))
Expand All @@ -51,7 +51,7 @@ def __generate_access_token(*, account: Account) -> AccessToken:
@staticmethod
def verify_access_token(*, token: str) -> AccessTokenPayload:

jwt_signing_key = ConfigService.get_token_signing_key()
jwt_signing_key = ConfigService.get_value(key="TOKEN_SIGNING_KEY", section="ACCOUNTS")

try:
verified_token = jwt.decode(token, jwt_signing_key, algorithms=["HS256"])
Expand Down
4 changes: 2 additions & 2 deletions src/apps/backend/modules/application/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ApplicationRepositoryClient:

@classmethod
def get_client(cls) -> MongoClient:
connection_caching = ConfigService.get_bool("MONGODB_CONNECTION_CACHING")
connection_caching = ConfigService.get_value(key="MONGODB_CONNECTION_CACHING", section="SERVER_CONFIG")=="True"

if connection_caching:
if cls._client is None:
Expand All @@ -27,7 +27,7 @@ def get_client(cls) -> MongoClient:

@staticmethod
def _create_client() -> MongoClient:
connection_uri = ConfigService.get_string("MONGODB_URI")
connection_uri = ConfigService.get_value(key="URI", section="MONGODB")
Logger.info(message=f"connecting to database - {connection_uri}")
client = MongoClient(connection_uri, server_api=ServerApi("1"))
Logger.info(message=f"connected to database - {connection_uri}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ def send_email(params: SendEmailParams) -> None:
@staticmethod
def get_client() -> sendgrid.SendGridAPIClient:
if not SendGridService.__client:
api_key = ConfigService.get_sendgrid_api_key()
api_key = ConfigService.get_value(key="API_KEY",section="SENDGRID")
SendGridService.__client = sendgrid.SendGridAPIClient(api_key=api_key)
return SendGridService.__client
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def send_sms(params: SendSMSParams) -> None:
# Send SMS
client.messages.create(
to=params.recipient_phone,
messaging_service_sid=ConfigService.get_twilio_config(key="messaging_service_sid"),
messaging_service_sid=ConfigService.get_value(key="MESSAGING_SERVICE_SID",section="TWILIO"),
body=params.message_body,
)

Expand All @@ -32,8 +32,8 @@ def send_sms(params: SendSMSParams) -> None:
@staticmethod
def get_client() -> Client:
if not TwilioService.__client:
account_sid = ConfigService.get_twilio_config(key="account_sid")
auth_token = ConfigService.get_twilio_config(key="auth_token")
account_sid = ConfigService.get_value(key="ACCOUNT_SID",section="TWILIO")
auth_token = ConfigService.get_value(key="AUTH_TOKEN",section="TWILIO")

# Initialize the Twilio client
TwilioService.__client = Client(account_sid, auth_token)
Expand Down
3 changes: 1 addition & 2 deletions src/apps/backend/modules/communication/sms_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
class SMSService:
@staticmethod
def send_sms(*, params: SendSMSParams) -> None:
is_sms_enabled = ConfigService.get_bool("SMS_ENABLED")

is_sms_enabled = ConfigService.get_value(key="SMS_ENABLED",section="SMS") == "True"
if not is_sms_enabled:
Logger.warn(message=f"SMS is disabled. Could not send message - {params.message_body}")
return
Expand Down
42 changes: 0 additions & 42 deletions src/apps/backend/modules/config/config_manager.py

This file was deleted.

146 changes: 75 additions & 71 deletions src/apps/backend/modules/config/config_service.py
Original file line number Diff line number Diff line change
@@ -1,74 +1,78 @@
from modules.common.dict_util import DictUtil
from modules.config.config_manager import ConfigManager
from modules.config.types import PapertrailConfig
import configparser
import os
from typing import Any
from pathlib import Path
from modules.error.custom_errors import MissingKeyError
from modules.common.types import ErrorCode
import json


class ConfigService:
@staticmethod
def get_string(key: str) -> str:
return DictUtil.required_get_str(input_dict=ConfigManager.config, key=key)

@staticmethod
def get_bool(key: str) -> bool:
return DictUtil.required_get_bool(input_dict=ConfigManager.config, key=key)

@staticmethod
def get_db_uri() -> str:
return DictUtil.required_get_str(input_dict=ConfigManager.config, key="MONGODB_URI")

@staticmethod
def get_logger_transports() -> tuple:
return DictUtil.required_get_tuple(input_dict=ConfigManager.config, key="LOGGER_TRANSPORTS")

@staticmethod
def get_papertrail_config() -> PapertrailConfig:
return PapertrailConfig(
host=DictUtil.required_get_str(input_dict=ConfigManager.config, key="PAPERTRAIL_HOST"),
port=int(DictUtil.required_get_str(input_dict=ConfigManager.config, key="PAPERTRAIL_PORT")),
)

@staticmethod
def get_accounts_config() -> dict:
return DictUtil.required_get_dict(input_dict=ConfigManager.config, key="ACCOUNTS")

@staticmethod
def get_token_signing_key() -> str:
return DictUtil.required_get_str(input_dict=ConfigService.get_accounts_config(), key="token_signing_key")

@staticmethod
def get_token_expiry_days() -> int:
return DictUtil.required_get_int(input_dict=ConfigService.get_accounts_config(), key="token_expiry_days")
def get_parent_directory(directory: str, levels: int) -> Path:
parent_dir = Path(directory)
for _ in range(levels):
parent_dir = parent_dir.parent
return parent_dir

@staticmethod
def get_web_app_host() -> str:
return DictUtil.required_get_str(input_dict=ConfigManager.config, key="WEB_APP_HOST")

@staticmethod
def get_sendgrid_api_key() -> str:
return str(DictUtil.required_get_dict(input_dict=ConfigManager.config, key="SENDGRID")["api_key"])

@staticmethod
def get_mailer_config(key: str) -> str:
return str(DictUtil.required_get_dict(input_dict=ConfigManager.config, key="MAILER")[key])

@staticmethod
def get_password_reset_token() -> dict:
return DictUtil.required_get_dict(input_dict=ConfigManager.config, key="PASSWORD_RESET_TOKEN")

@staticmethod
def get_twilio_config(key: str) -> str:
return str(DictUtil.required_get_dict(input_dict=ConfigManager.config, key="TWILIO")[key])

@staticmethod
def get_otp_config(key: str) -> str:
return str(DictUtil.required_get_dict(input_dict=ConfigManager.config, key="OTP")[key])

@staticmethod
def has_key(key: str) -> bool:
return key in ConfigManager.config

@staticmethod
def has_default_phone_number() -> bool:
if ConfigService.has_key("OTP") and "default_phone_number" in ConfigManager.config["OTP"]:
return True
return False
class ConfigService:
_config = None
config_path = get_parent_directory(__file__, 6) / "config"

@staticmethod
def load_config():
config = configparser.ConfigParser()
default_config = ConfigService.config_path / "default.ini"
app_env = os.environ.get('APP_ENV', "development")
app_env_config = ConfigService.config_path / f"{app_env}.ini"
config.read([default_config, app_env_config])
ConfigService.__ensure_all_sections_exist(config, default_config)
ConfigService.__load_environment_variables(config=config)
ConfigService._config = config
config_dict = {
section: {
key: value
for key, value in ConfigService._config[section].items()
}
for section in ConfigService._config.sections()
}
print("config:", config_dict)

@staticmethod
def __ensure_all_sections_exist(config: configparser.ConfigParser, default_config_path: Path):
default_config = configparser.ConfigParser()
default_config.read(default_config_path)

for section in default_config.sections():
if section not in config:
config.add_section(section)

@staticmethod
def __load_environment_variables(config: configparser.ConfigParser):
env_config = configparser.ConfigParser()
custom_env_file = ConfigService.config_path / "custom-environment-variables.ini"

if custom_env_file.exists():
env_config.read(custom_env_file)
config.read(custom_env_file)

for section in env_config.sections():
if section not in config:
config.add_section(section)
for key, value in env_config[section].items():
env_var = os.environ.get(value)
if env_var is not None:
config[section][key] = env_var

@staticmethod
def get_value(*, key: str, section: str = 'DEFAULT') -> Any:
try:
value = ConfigService._config.get(section, key, fallback=None)
return value if value else None
except (configparser.NoOptionError, configparser.NoSectionError):
raise MissingKeyError(missing_key=key, error_code=ErrorCode.MISSING_KEY)

@staticmethod
def has_value(*, key: str, section: str = 'DEFAULT') -> bool:
if ConfigService._config.has_option(section, key):
value = ConfigService._config.get(section, key, fallback=None)
return bool(value)
return False
2 changes: 1 addition & 1 deletion src/apps/backend/modules/logger/internal/loggers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Loggers:

@staticmethod
def initialize_loggers() -> None:
logger_transports = ConfigService.get_logger_transports()
logger_transports = tuple(ConfigService.get_value(key='LOGGER_TRANSPORTS', section='LOGGER').split(","))
for logger_transport in logger_transports:
if logger_transport == LoggerTransports.CONSOLE:
Loggers._loggers.append(Loggers.__get_console_logger())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from logging.handlers import SysLogHandler

from modules.config.types import PapertrailConfig
from modules.config.config_service import ConfigService
from modules.logger.internal.base_logger import BaseLogger

Expand All @@ -11,7 +12,10 @@ def __init__(self) -> None:
self.logger.setLevel(logging.INFO)

# Create a console handler and set the level to INFO
logger_config = ConfigService.get_papertrail_config()
logger_config = PapertrailConfig(
host=ConfigService.get_value(key='HOST', section='PAPERTRAIL'),
port=ConfigService.get_value(key='PORT', section='PAPERTRAIL')
)
papertrail_handler = SysLogHandler(address=(logger_config.host, logger_config.port))
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
papertrail_handler.setFormatter(formatter)
Expand Down
Loading

0 comments on commit d9bc842

Please sign in to comment.