Create queries table

This commit is contained in:
Keith Edmunds 2025-02-26 13:34:10 +00:00
parent 64ccb485b5
commit 985629446a
4 changed files with 110 additions and 8 deletions

View File

@ -18,7 +18,8 @@ class DatabaseManager:
def __init__(self, database_url: str, **kwargs: dict) -> None:
if DatabaseManager.__instance is None:
self.db = Alchemical(database_url, **kwargs)
self.db.create_all()
# Database managed by Alembic so no create_all() required
# self.db.create_all()
DatabaseManager.__instance = self
else:
raise Exception("Attempted to create a second DatabaseManager instance")

View File

@ -1,6 +1,8 @@
# Standard library imports
from typing import Optional
from dataclasses import asdict
import datetime as dt
import json
# PyQt imports
@ -13,13 +15,37 @@ from sqlalchemy import (
String,
)
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.engine.interfaces import Dialect
from sqlalchemy.orm import (
Mapped,
mapped_column,
relationship,
)
from sqlalchemy.types import TypeDecorator, TEXT
# App imports
from classes import Filter
class JSONEncodedDict(TypeDecorator):
"""
Custom JSON Type for MariaDB (since native JSON type is just LONGTEXT)
"""
impl = TEXT
def process_bind_param(self, value: dict | None, dialect: Dialect) -> str | None:
"""Convert Python dictionary to JSON string before saving."""
if value is None:
return None
return json.dumps(value, default=lambda o: o.__dict__)
def process_result_value(self, value: str | None, dialect: Dialect) -> dict | None:
"""Convert JSON string back to Python dictionary after retrieval."""
if value is None:
return None
return json.loads(value)
# Database classes
@ -128,15 +154,24 @@ class QueriesTable(Model):
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(128), nullable=False)
sql: Mapped[str] = mapped_column(
String(2048), index=False, default="", nullable=False
)
favourite: Mapped[bool] = mapped_column(
Boolean, nullable=False, index=False, default=False
)
_filter_data: Mapped[dict | None] = mapped_column("filter_data", JSONEncodedDict, nullable=True)
favourite: Mapped[bool] = mapped_column(Boolean, nullable=False, index=False, default=False)
def _get_filter(self) -> Filter:
"""Convert stored JSON dictionary to a Filter object."""
if isinstance(self._filter_data, dict):
return Filter(**self._filter_data)
return Filter() # Default object if None or invalid data
def _set_filter(self, value: Filter | None) -> None:
"""Convert a Filter object to JSON before storing."""
self._filter_data = asdict(value) if isinstance(value, Filter) else None
# Single definition of `filter`
filter = property(_get_filter, _set_filter)
def __repr__(self) -> str:
return f"<Queries(id={self.id}, name={self.name}, sql={self.sql}>"
return f"<QueriesTable(id={self.id}, name={self.name}, filter={self.filter})>"
class SettingsTable(Model):

View File

@ -608,6 +608,25 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
session.connection().execute(stmt, sqla_map)
class Queries(dbtables.QueriesTable):
def __init__(
self, session: Session, name: str, filter: dbtables.Filter, favourite: bool = False
) -> None:
"""Create new query"""
self.name = name
self.filter = filter
self.favourite = favourite
session.add(self)
session.commit()
@classmethod
def get_all_queries(cls, session: Session) -> Sequence["Queries"]:
"""Returns a list of all queries ordered by name"""
return session.scalars(select(cls).order_by(cls.name)).all()
class Settings(dbtables.SettingsTable):
def __init__(self, session: Session, name: str) -> None:
self.name = name

View File

@ -0,0 +1,47 @@
"""create queries table
Revision ID: 4fc2a9a82ab0
Revises: ab475332d873
Create Date: 2025-02-26 13:13:25.118489
"""
from alembic import op
import sqlalchemy as sa
import dbtables
# revision identifiers, used by Alembic.
revision = '4fc2a9a82ab0'
down_revision = 'ab475332d873'
branch_labels = None
depends_on = None
def upgrade(engine_name: str) -> None:
globals()["upgrade_%s" % engine_name]()
def downgrade(engine_name: str) -> None:
globals()["downgrade_%s" % engine_name]()
def upgrade_() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('queries',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.String(length=128), nullable=False),
sa.Column('filter_data', dbtables.JSONEncodedDict(), nullable=True),
sa.Column('favourite', sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade_() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('queries')
# ### end Alembic commands ###