Skip to content

Commit

Permalink
refact: SettingManager for streamlined settings retrieval and toggling
Browse files Browse the repository at this point in the history
  • Loading branch information
erfjab committed Nov 7, 2024
1 parent be09f56 commit 493bf46
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 117 deletions.
1 change: 1 addition & 0 deletions db/alembic/versions/3e5deef43bf0_init_commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Revises:
Create Date: 2024-10-11 15:47:37.464534
"""
# pylint: disable=all

from typing import Sequence, Union
from alembic import op
Expand Down
52 changes: 52 additions & 0 deletions db/alembic/versions/4abf3adb8ab8_refact_setting_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Refactor settings table
Revision ID: 4abf3adb8ab8
Revises: ab1ce3ef2a57
Create Date: 2024-11-08 02:42:49.391685
"""
# pylint: disable=all

from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa

# Revision identifiers, used by Alembic.
revision: str = "4abf3adb8ab8"
down_revision: Union[str, None] = "ab1ce3ef2a57"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Refactor settings table by dropping the old table and creating a new structure."""
# Drop the old settings table
op.drop_table("settings") # pylint: disable=no-member

# Recreate the settings table with the new structure
op.create_table(
"settings",
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
sa.Column("node_monitoring", sa.Boolean(), nullable=False),
sa.Column("node_auto_restart", sa.Boolean(), nullable=False),
) # pylint: disable=no-member


def downgrade() -> None:
"""Revert the settings table to its original structure."""
# Drop the new settings table
op.drop_table("settings") # pylint: disable=no-member

# Recreate the original settings table structure
op.create_table(
"settings",
sa.Column("key", sa.VARCHAR(length=256), nullable=False),
sa.Column("value", sa.VARCHAR(length=2048), nullable=True),
sa.Column(
"created_at",
sa.DATETIME(),
server_default=sa.text("(CURRENT_TIMESTAMP)"),
nullable=False,
),
sa.Column("updated_at", sa.DATETIME(), nullable=True),
) # pylint: disable=no-member
1 change: 1 addition & 0 deletions db/alembic/versions/ab1ce3ef2a57_add_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Revises: 3e5deef43bf0
Create Date: 2024-10-13 01:42:55.733416
"""
# pylint: disable=all

from typing import Sequence, Union
from alembic import op
Expand Down
87 changes: 41 additions & 46 deletions db/crud/setting.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,62 @@
"""
This module provides functionality for managing settings in the application.
It includes methods for upserting and retrieving settings from the database.
This module provides functionality for managing application settings,
including methods for updating and retrieving settings.
"""

from sqlalchemy.future import select
from db.base import get_db
from db.models import Setting
from models import SettingData, SettingUpsert, SettingKeys
from models import SettingKeys


class SettingManager:
"""Manager class for handling settings operations."""
"""Handles the application's settings management, ensuring a single settings record exists."""

@staticmethod
async def upsert(setting_upsert: SettingUpsert) -> SettingData | None:
async def get(field: SettingKeys) -> bool:
"""
Upsert a setting in the database.
If the setting exists and the value is None, it will be deleted.
If the setting does not exist, it will be created.
Args:
setting_upsert (SettingUpsert): The setting data to upsert.
Returns:
SettingData | None: The upserted setting data or None if deleted.
Retrieve the specified setting field, ensuring the settings record exists.
If the settings record does not exist, it will be created.
"""
async with get_db() as db:
existing_setting = await db.execute(
select(Setting).where(Setting.key == setting_upsert.key)
)
setting = existing_setting.scalar_one_or_none()
# Attempt to retrieve the settings record
result = await db.execute(select(Setting))
settings = result.scalar_one_or_none()

if setting:
if setting_upsert.value is None:
await db.delete(setting)
await db.commit()
return None
setting.value = setting_upsert.value
else:
if setting_upsert.value is not None:
setting = Setting(
key=setting_upsert.key, value=setting_upsert.value
)
db.add(setting)
# If no settings record, create it
if not settings:
settings = Setting()
db.add(settings)
await db.commit()
await db.refresh(settings) # Refresh after commit to access attributes

await db.commit()
await db.refresh(setting)
return SettingData.from_orm(setting)
# Return the value of the specified field
return getattr(settings, field.value)

@staticmethod
async def get(key: SettingKeys) -> SettingData | None:
async def toggle_field(field: SettingKeys) -> bool:
"""
Retrieve a setting by its key.
Args:
key (SettingKeys): The key of the setting to retrieve.
Returns:
SettingData | None: The retrieved setting data or None if not found.
Toggle a boolean setting field and return its new value.
Ensures the settings record exists before toggling.
"""
async with get_db() as db:
result = await db.execute(select(Setting).where(Setting.key == key))
setting = result.scalar_one_or_none()
return SettingData.from_orm(setting) if setting else None
# Retrieve or create the settings record
result = await db.execute(select(Setting))
settings = result.scalar_one_or_none()

if not settings:
settings = Setting()
db.add(settings)
await db.commit()
await db.refresh(settings)

# Toggle the field's current value
current_value = getattr(settings, field.value)
new_value = not current_value
setattr(settings, field.value, new_value)

# Commit the change
db.add(settings)
await db.commit()

return new_value
18 changes: 8 additions & 10 deletions db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""

from datetime import datetime
from sqlalchemy import Integer, DateTime, String
from sqlalchemy import Integer, DateTime, String, Boolean
from sqlalchemy.orm import Mapped, mapped_column
from db.base import Base

Expand All @@ -26,15 +26,13 @@ class Token(Base):


class Setting(Base):
"""Model representing a setting."""
"""
Model representing application settings.
Only one record should exist in this table at any time.
"""

__tablename__ = "settings"

key: Mapped[str] = mapped_column(String(256), primary_key=True)
value: Mapped[str] = mapped_column(String(2048))
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.now, nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), onupdate=datetime.now, nullable=True
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
node_monitoring: Mapped[bool] = mapped_column(Boolean, default=False)
node_auto_restart: Mapped[bool] = mapped_column(Boolean, default=False)
8 changes: 2 additions & 6 deletions jobs/node_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@

async def node_checker():
"""Check the status of nodes and perform actions based on their status."""
node_checker_is_active = await SettingManager.get(
SettingKeys.NODE_MONITORING_IS_ACTIVE
)
node_checker_is_active = await SettingManager.get(SettingKeys.NODE_MONITORING)
if not node_checker_is_active:
return

Expand All @@ -35,9 +33,7 @@ async def node_checker():
anti_spam = True
await report.node_error(node)

node_auto_restart = await SettingManager.get(
SettingKeys.NODE_MONITORING_AUTO_RESTART
)
node_auto_restart = await SettingManager.get(SettingKeys.NODE_AUTO_RESTART)
if not node_auto_restart:
continue

Expand Down
4 changes: 1 addition & 3 deletions models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from .token import TokenData, TokenUpsert
from .state import UserCreateForm
from .setting import SettingData, SettingUpsert, SettingKeys
from .setting import SettingKeys
from .callback import (
PagesActions,
PagesCallbacks,
Expand All @@ -21,8 +21,6 @@
"TokenData",
"TokenUpsert",
"UserCreateForm",
"SettingData",
"SettingUpsert",
"SettingKeys",
"PagesActions",
"PagesCallbacks",
Expand Down
33 changes: 4 additions & 29 deletions models/setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,11 @@
Module defining settings models for application configuration.
"""

from datetime import datetime
from enum import Enum
from typing import Optional
from pydantic import BaseModel


class SettingKeys(str, Enum):
"""Enum for application setting keys."""
class SettingKeys(Enum):
"""Enum for settings table columns."""

NODE_MONITORING_IS_ACTIVE = "node_monitoring_is_active"
NODE_MONITORING_AUTO_RESTART = "node_monitoring_auto_restart"


class SettingData(BaseModel):
"""Model for application setting data."""

key: str
value: Optional[str]
created_at: datetime
updated_at: Optional[datetime]

# pylint: disable=R0903
class Config:
"""Pydantic configuration options."""

from_attributes = True


class SettingUpsert(BaseModel):
"""Model for upserting a setting."""

key: str
value: Optional[str]
NODE_MONITORING = "node_monitoring"
NODE_AUTO_RESTART = "node_auto_restart"
27 changes: 4 additions & 23 deletions routers/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,21 @@
PagesActions,
PagesCallbacks,
SettingKeys,
SettingUpsert,
ConfirmCallbacks,
BotActions,
)

router = Router()


async def get_setting_status(key: SettingKeys) -> str:
"""
Returns the status of the specified setting as 'ON' or 'OFF'.
"""
return "ON" if await SettingManager.get(key) else "OFF"


async def toggle_setting(key: SettingKeys):
"""
Toggles the value of the specified setting.
"""
current_value = await SettingManager.get(key)
new_value = None if current_value else "True"
await SettingManager.upsert(SettingUpsert(key=key, value=new_value))


@router.callback_query(PagesCallbacks.filter(F.page.is_(PagesActions.NODE_MONITORING)))
async def node_monitoring_menu(callback: CallbackQuery):
"""
Handler for the node monitoring menu callback. It retrieves the current status
of node monitoring settings and updates the menu text.
"""
checker_status = await get_setting_status(SettingKeys.NODE_MONITORING_IS_ACTIVE)
auto_restart_status = await get_setting_status(
SettingKeys.NODE_MONITORING_AUTO_RESTART
)
checker_status = await SettingManager.get(SettingKeys.NODE_MONITORING)
auto_restart_status = await SettingManager.get(SettingKeys.NODE_AUTO_RESTART)

text = MessageTexts.NODE_MONITORING_MENU.format(
checker=checker_status,
Expand All @@ -63,7 +44,7 @@ async def node_monitoring_auto_restart(callback: CallbackQuery):
"""
Handler for toggling the auto-restart setting for node monitoring.
"""
await toggle_setting(SettingKeys.NODE_MONITORING_AUTO_RESTART)
await SettingManager.toggle_field(SettingKeys.NODE_AUTO_RESTART)
await node_monitoring_menu(callback)


Expand All @@ -72,5 +53,5 @@ async def node_monitoring_checker(callback: CallbackQuery):
"""
Handler for toggling the checker setting for node monitoring.
"""
await toggle_setting(SettingKeys.NODE_MONITORING_IS_ACTIVE)
await SettingManager.toggle_field(SettingKeys.NODE_MONITORING)
await node_monitoring_menu(callback)

0 comments on commit 493bf46

Please sign in to comment.