Migrate to Alchemical

This commit is contained in:
Keith Edmunds 2024-04-02 21:42:22 +01:00
parent 6890e0d0c2
commit 9d44642fea
10 changed files with 321 additions and 280 deletions

View File

@ -1,13 +1,18 @@
# Standard library imports
from dataclasses import dataclass
import datetime as dt
from typing import Optional
import datetime as dt
# PyQt imports
from PyQt6.QtCore import pyqtSignal, QObject, QThread
# Third party imports
import numpy as np
import pyqtgraph as pg # type: ignore
from sqlalchemy.orm import scoped_session
# App imports
from config import Config
from dbconfig import scoped_session
from models import PlaylistRows
import helpers

View File

@ -1,38 +0,0 @@
import inspect
import os
from config import Config
from contextlib import contextmanager
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from typing import Generator
from log import log
MYSQL_CONNECT = os.environ.get("MM_DB")
if MYSQL_CONNECT is None:
raise ValueError("MYSQL_CONNECT is undefined")
else:
dbname = MYSQL_CONNECT.split("/")[-1]
log.debug(f"Database: {dbname}")
engine = create_engine(
MYSQL_CONNECT,
echo=Config.DISPLAY_SQL,
pool_pre_ping=True,
future=True,
connect_args={"charset": "utf8mb4"},
)
@contextmanager
def Session() -> Generator[scoped_session, None, None]:
frame = inspect.stack()[2]
file = frame.filename
function = frame.function
lineno = frame.lineno
Session = scoped_session(sessionmaker(bind=engine))
log.debug(f"Session acquired: {file}:{function}:{lineno} " f"[{hex(id(Session))}]")
yield Session
log.debug(f" Session released [{hex(id(Session))}]")
Session.commit()
Session.close()

187
app/dbtables.py Normal file
View File

@ -0,0 +1,187 @@
# Standard library imports
from typing import List, Optional
import datetime as dt
import os
# PyQt imports
# Third party imports
from alchemical import Alchemical, Model # type: ignore
from sqlalchemy import (
Boolean,
DateTime,
ForeignKey,
String,
)
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import (
Mapped,
mapped_column,
relationship,
)
# App imports
# Database classes
# Note: initialisation of the 'db' variable is at the foot of this
# module.
class CartsTable(Model):
__tablename__ = "carts"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
cart_number: Mapped[int] = mapped_column(unique=True)
name: Mapped[str] = mapped_column(String(256), index=True)
duration: Mapped[Optional[int]] = mapped_column(index=True)
path: Mapped[Optional[str]] = mapped_column(String(2048), index=False)
enabled: Mapped[Optional[bool]] = mapped_column(default=False)
def __repr__(self) -> str:
return (
f"<Carts(id={self.id}, cart={self.cart_number}, "
f"name={self.name}, path={self.path}>"
)
class NoteColoursTable(Model):
__tablename__ = "notecolours"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
substring: Mapped[str] = mapped_column(String(256), index=False)
colour: Mapped[str] = mapped_column(String(21), index=False)
enabled: Mapped[bool] = mapped_column(default=True, index=True)
is_regex: Mapped[bool] = mapped_column(default=False, index=False)
is_casesensitive: Mapped[bool] = mapped_column(default=False, index=False)
order: Mapped[Optional[int]] = mapped_column(index=True)
def __repr__(self) -> str:
return (
f"<NoteColour(id={self.id}, substring={self.substring}, "
f"colour={self.colour}>"
)
class PlaydatesTable(Model):
__tablename__ = "playdates"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
lastplayed: Mapped[dt.datetime] = mapped_column(index=True)
track_id: Mapped[int] = mapped_column(ForeignKey("tracks.id"))
track: Mapped["TracksTable"] = relationship("Tracks", back_populates="playdates")
def __repr__(self) -> str:
return (
f"<Playdates(id={self.id}, track_id={self.track_id} "
f"lastplayed={self.lastplayed}>"
)
class PlaylistsTable(Model):
"""
Manage playlists
"""
__tablename__ = "playlists"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(32), unique=True)
last_used: Mapped[Optional[dt.datetime]] = mapped_column(DateTime, default=None)
tab: Mapped[Optional[int]] = mapped_column(default=None)
open: Mapped[bool] = mapped_column(default=False)
is_template: Mapped[bool] = mapped_column(default=False)
deleted: Mapped[bool] = mapped_column(default=False)
rows: Mapped[List["PlaylistRowsTable"]] = relationship(
"PlaylistRows",
back_populates="playlist",
cascade="all, delete-orphan",
order_by="PlaylistRows.plr_rownum",
)
def __repr__(self) -> str:
return (
f"<Playlists(id={self.id}, name={self.name}, "
f"is_templatee={self.is_template}, open={self.open}>"
)
class PlaylistRowsTable(Model):
__tablename__ = "playlist_rows"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
plr_rownum: Mapped[int]
note: Mapped[str] = mapped_column(
String(2048), index=False, default="", nullable=False
)
playlist_id: Mapped[int] = mapped_column(ForeignKey("playlists.id"))
playlist: Mapped[PlaylistsTable] = relationship(back_populates="rows")
track_id: Mapped[Optional[int]] = mapped_column(ForeignKey("tracks.id"))
track: Mapped["TracksTable"] = relationship(
"Tracks",
back_populates="playlistrows",
)
played: Mapped[bool] = mapped_column(
Boolean, nullable=False, index=False, default=False
)
def __repr__(self) -> str:
return (
f"<PlaylistRow(id={self.id}, playlist_id={self.playlist_id}, "
f"track_id={self.track_id}, "
f"note={self.note}, plr_rownum={self.plr_rownum}>"
)
class SettingsTable(Model):
"""Manage settings"""
__tablename__ = "settings"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(64), unique=True)
f_datetime: Mapped[Optional[dt.datetime]] = mapped_column(default=None)
f_int: Mapped[Optional[int]] = mapped_column(default=None)
f_string: Mapped[Optional[str]] = mapped_column(String(128), default=None)
def __repr__(self) -> str:
return (
f"<Settings(id={self.id}, name={self.name}, "
f"f_datetime={self.f_datetime}, f_int={self.f_int}, f_string={self.f_string}>"
)
class TracksTable(Model):
__tablename__ = "tracks"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
title: Mapped[str] = mapped_column(String(256), index=True)
artist: Mapped[str] = mapped_column(String(256), index=True)
bitrate: Mapped[Optional[int]] = mapped_column(default=None)
duration: Mapped[int] = mapped_column(index=True)
fade_at: Mapped[int] = mapped_column(index=False)
mtime: Mapped[float] = mapped_column(index=True)
path: Mapped[str] = mapped_column(String(2048), index=False, unique=True)
silence_at: Mapped[int] = mapped_column(index=False)
start_gap: Mapped[int] = mapped_column(index=False)
playlistrows: Mapped[List[PlaylistRowsTable]] = relationship(
"PlaylistRows", back_populates="track"
)
playlists = association_proxy("playlistrows", "playlist")
playdates: Mapped[List[PlaydatesTable]] = relationship(
"Playdates",
back_populates="track",
lazy="joined",
)
def __repr__(self) -> str:
return (
f"<Track(id={self.id}, title={self.title}, "
f"artist={self.artist}, path={self.path}>"
)
MYSQL_CONNECT = os.environ.get("MM_DB")
if MYSQL_CONNECT is None:
raise ValueError("MYSQL_CONNECT is undefined")
else:
dbname = MYSQL_CONNECT.split("/")[-1]
db = Alchemical(MYSQL_CONNECT)

View File

@ -1,10 +1,17 @@
# Standard library imports
# PyQt imports
# Third party imports
# App imports
from typing import Optional
from PyQt6.QtCore import QEvent, Qt
from PyQt6.QtWidgets import QDialog, QListWidgetItem
from classes import MusicMusterSignals
from dbconfig import scoped_session
from sqlalchemy.orm import scoped_session
from helpers import (
ask_yes_no,
get_relative_date,

View File

@ -1,63 +1,37 @@
#!/usr/bin/python3
# Standard library imports
from typing import List, Optional, Sequence
import datetime as dt
import re
from config import Config
from dbconfig import scoped_session
import datetime as dt
from typing import List, Optional, Sequence
from sqlalchemy.ext.associationproxy import association_proxy
# PyQt imports
# Third party imports
from sqlalchemy import (
bindparam,
Boolean,
DateTime,
delete,
ForeignKey,
func,
select,
String,
update,
)
from sqlalchemy.orm import (
DeclarativeBase,
joinedload,
Mapped,
mapped_column,
relationship,
)
from sqlalchemy.orm.exc import (
NoResultFound,
)
from sqlalchemy.exc import (
IntegrityError,
)
from sqlalchemy.orm import (
joinedload,
scoped_session,
)
from sqlalchemy.orm.exc import (
NoResultFound,
)
# App imports
import dbtables
from config import Config
from log import log
class Base(DeclarativeBase):
pass
# Database classes
class Carts(Base):
__tablename__ = "carts"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
cart_number: Mapped[int] = mapped_column(unique=True)
name: Mapped[str] = mapped_column(String(256), index=True)
duration: Mapped[Optional[int]] = mapped_column(index=True)
path: Mapped[Optional[str]] = mapped_column(String(2048), index=False)
enabled: Mapped[Optional[bool]] = mapped_column(default=False)
def __repr__(self) -> str:
return (
f"<Carts(id={self.id}, cart={self.cart_number}, "
f"name={self.name}, path={self.path}>"
)
class Carts(dbtables.CartsTable):
def __init__(
self,
@ -80,22 +54,7 @@ class Carts(Base):
session.commit()
class NoteColours(Base):
__tablename__ = "notecolours"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
substring: Mapped[str] = mapped_column(String(256), index=False)
colour: Mapped[str] = mapped_column(String(21), index=False)
enabled: Mapped[bool] = mapped_column(default=True, index=True)
is_regex: Mapped[bool] = mapped_column(default=False, index=False)
is_casesensitive: Mapped[bool] = mapped_column(default=False, index=False)
order: Mapped[Optional[int]] = mapped_column(index=True)
def __repr__(self) -> str:
return (
f"<NoteColour(id={self.id}, substring={self.substring}, "
f"colour={self.colour}>"
)
class NoteColours(dbtables.NoteColoursTable):
def __init__(
self,
@ -157,19 +116,7 @@ class NoteColours(Base):
return None
class Playdates(Base):
__tablename__ = "playdates"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
lastplayed: Mapped[dt.datetime] = mapped_column(index=True)
track_id: Mapped[int] = mapped_column(ForeignKey("tracks.id"))
track: Mapped["Tracks"] = relationship("Tracks", back_populates="playdates")
def __repr__(self) -> str:
return (
f"<Playdates(id={self.id}, track_id={self.track_id} "
f"lastplayed={self.lastplayed}>"
)
class Playdates(dbtables.PlaydatesTable):
def __init__(self, session: scoped_session, track_id: int) -> None:
"""Record that track was played"""
@ -208,32 +155,7 @@ class Playdates(Base):
).all()
class Playlists(Base):
"""
Manage playlists
"""
__tablename__ = "playlists"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(32), unique=True)
last_used: Mapped[Optional[dt.datetime]] = mapped_column(DateTime, default=None)
tab: Mapped[Optional[int]] = mapped_column(default=None)
open: Mapped[bool] = mapped_column(default=False)
is_template: Mapped[bool] = mapped_column(default=False)
deleted: Mapped[bool] = mapped_column(default=False)
rows: Mapped[List["PlaylistRows"]] = relationship(
"PlaylistRows",
back_populates="playlist",
cascade="all, delete-orphan",
order_by="PlaylistRows.plr_rownum",
)
def __repr__(self) -> str:
return (
f"<Playlists(id={self.id}, name={self.name}, "
f"is_templatee={self.is_template}, open={self.open}>"
)
class Playlists(dbtables.PlaylistsTable):
def __init__(self, session: scoped_session, name: str):
self.name = name
@ -366,31 +288,7 @@ class Playlists(Base):
PlaylistRows.copy_playlist(session, playlist_id, template.id)
class PlaylistRows(Base):
__tablename__ = "playlist_rows"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
plr_rownum: Mapped[int]
note: Mapped[str] = mapped_column(
String(2048), index=False, default="", nullable=False
)
playlist_id: Mapped[int] = mapped_column(ForeignKey("playlists.id"))
playlist: Mapped[Playlists] = relationship(back_populates="rows")
track_id: Mapped[Optional[int]] = mapped_column(ForeignKey("tracks.id"))
track: Mapped["Tracks"] = relationship(
"Tracks",
back_populates="playlistrows",
)
played: Mapped[bool] = mapped_column(
Boolean, nullable=False, index=False, default=False
)
def __repr__(self) -> str:
return (
f"<PlaylistRow(id={self.id}, playlist_id={self.playlist_id}, "
f"track_id={self.track_id}, "
f"note={self.note}, plr_rownum={self.plr_rownum}>"
)
class PlaylistRows(dbtables.PlaylistRowsTable):
def __init__(
self,
@ -669,27 +567,7 @@ class PlaylistRows(Base):
session.connection().execute(stmt, sqla_map)
class Settings(Base):
"""Manage settings"""
__tablename__ = "settings"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(64), unique=True)
f_datetime: Mapped[Optional[dt.datetime]] = mapped_column(default=None)
f_int: Mapped[Optional[int]] = mapped_column(default=None)
f_string: Mapped[Optional[str]] = mapped_column(String(128), default=None)
def __repr__(self) -> str:
return (
f"<Settings(id={self.id}, name={self.name}, "
f"f_datetime={self.f_datetime}, f_int={self.f_int}, f_string={self.f_string}>"
)
def __init__(self, session: scoped_session, name: str):
self.name = name
session.add(self)
session.flush()
class Settings(dbtables.SettingsTable):
@classmethod
def all_as_dict(cls, session):
@ -722,28 +600,7 @@ class Settings(Base):
session.flush()
class Tracks(Base):
__tablename__ = "tracks"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
title: Mapped[str] = mapped_column(String(256), index=True)
artist: Mapped[str] = mapped_column(String(256), index=True)
bitrate: Mapped[Optional[int]] = mapped_column(default=None)
duration: Mapped[int] = mapped_column(index=True)
fade_at: Mapped[int] = mapped_column(index=False)
mtime: Mapped[float] = mapped_column(index=True)
path: Mapped[str] = mapped_column(String(2048), index=False, unique=True)
silence_at: Mapped[int] = mapped_column(index=False)
start_gap: Mapped[int] = mapped_column(index=False)
playlistrows: Mapped[List[PlaylistRows]] = relationship(
"PlaylistRows", back_populates="track"
)
playlists = association_proxy("playlistrows", "playlist")
playdates: Mapped[List[Playdates]] = relationship(
"Playdates",
back_populates="track",
lazy="joined",
)
class Tracks(dbtables.TracksTable):
def __repr__(self) -> str:
return (

View File

@ -1,22 +1,17 @@
#!/usr/bin/env python3
import datetime as dt
from time import sleep
from typing import (
cast,
List,
Optional,
)
# Standard library imports
from os.path import basename
from time import sleep
from typing import cast, List, Optional
import argparse
import datetime as dt
import os
import subprocess
import sys
import threading
import pipeclient
from pygame import mixer
# PyQt imports
from PyQt6.QtCore import (
pyqtSignal,
QDate,
@ -49,8 +44,14 @@ from PyQt6.QtWidgets import (
QProgressBar,
QPushButton,
)
# Third party imports
from pygame import mixer
import pipeclient
from sqlalchemy.orm import scoped_session
import stackprinter # type: ignore
# App imports
from classes import (
track_sequence,
FadeCurve,
@ -58,23 +59,19 @@ from classes import (
PlaylistTrack,
)
from config import Config
from dbconfig import (
engine,
scoped_session,
Session,
)
from dbtables import db
from dialogs import TrackSelectDialog
from log import log
from models import Base, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
from models import Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
from playlistmodel import PlaylistModel, PlaylistProxyModel
from playlists import PlaylistTab
from ui import icons_rc # noqa F401
from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
from ui.main_window_ui import Ui_MainWindow # type: ignore
from utilities import check_db, update_bitrates
import helpers
from ui import icons_rc # noqa F401
import music
@ -168,7 +165,7 @@ class ImportTrack(QObject):
Create track objects from passed files and add to visible playlist
"""
with Session() as session:
with db.Session() as session:
for fname in self.filenames:
self.signals.status_message_signal.emit(
f"Importing {basename(fname)}", 5000
@ -255,7 +252,7 @@ class Window(QMainWindow, Ui_MainWindow):
except subprocess.CalledProcessError as exc_info:
git_tag = str(exc_info.output)
with Session() as session:
with db.Session() as session:
if session.bind:
dbname = session.bind.engine.url.database
@ -317,7 +314,7 @@ class Window(QMainWindow, Ui_MainWindow):
def cart_edit(self, btn: CartButton, event: QEvent):
"""Handle context menu for cart button"""
with Session() as session:
with db.Session() as session:
cart = session.query(Carts).get(btn.cart_id)
if cart is None:
log.error("cart_edit: cart not found")
@ -349,7 +346,7 @@ class Window(QMainWindow, Ui_MainWindow):
def carts_init(self) -> None:
"""Initialse carts data structures"""
with Session() as session:
with db.Session() as session:
# Number carts from 1 for humanity
for cart_number in range(1, Config.CARTS_COUNT + 1):
cart = session.query(Carts).get(cart_number)
@ -426,7 +423,7 @@ class Window(QMainWindow, Ui_MainWindow):
self, "Track playing", "Can't close application while track is playing"
)
else:
with Session() as session:
with db.Session() as session:
settings = Settings.all_as_dict(session)
record = settings["mainwindow_height"]
if record.f_int != self.height():
@ -495,7 +492,7 @@ class Window(QMainWindow, Ui_MainWindow):
return False
# Record playlist as closed and update remaining playlist tabs
with Session() as session:
with db.Session() as session:
playlist = session.get(Playlists, closing_tab_playlist_id)
if playlist:
playlist.close()
@ -588,7 +585,7 @@ class Window(QMainWindow, Ui_MainWindow):
def create_and_show_playlist(self) -> None:
"""Create new playlist and display it"""
with Session() as session:
with db.Session() as session:
playlist = self.create_playlist(session)
if playlist:
self.create_playlist_tab(playlist)
@ -636,7 +633,7 @@ class Window(QMainWindow, Ui_MainWindow):
Delete current playlist
"""
with Session() as session:
with db.Session() as session:
playlist_id = self.active_tab().playlist_id
playlist = session.get(Playlists, playlist_id)
if playlist:
@ -670,7 +667,7 @@ class Window(QMainWindow, Ui_MainWindow):
path += ".csv"
with open(path, "w") as f:
with Session() as session:
with db.Session() as session:
for playdate in Playdates.played_after(session, start_dt):
f.write(f"{playdate.track.artist},{playdate.track.title}\n")
@ -702,7 +699,7 @@ class Window(QMainWindow, Ui_MainWindow):
playlist_id = self.active_tab().playlist_id
with Session() as session:
with db.Session() as session:
# Get output filename
playlist = session.get(Playlists, playlist_id)
if not playlist:
@ -784,7 +781,7 @@ class Window(QMainWindow, Ui_MainWindow):
if not dlg.exec():
return
with Session() as session:
with db.Session() as session:
new_tracks = []
for fname in dlg.selectedFiles():
txt = ""
@ -883,7 +880,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.active_tab().source_model_selected_row_number()
or self.active_proxy_model().rowCount()
)
with Session() as session:
with db.Session() as session:
dlg = TrackSelectDialog(
session=session,
new_row_number=new_row_number,
@ -895,7 +892,7 @@ class Window(QMainWindow, Ui_MainWindow):
"""Load the playlists that were open when the last session closed"""
playlist_ids = []
with Session() as session:
with db.Session() as session:
for playlist in Playlists.get_open(session):
if playlist:
_ = self.create_playlist_tab(playlist)
@ -952,7 +949,7 @@ class Window(QMainWindow, Ui_MainWindow):
visible_tab = self.active_tab()
source_playlist_id = visible_tab.playlist_id
with Session() as session:
with db.Session() as session:
for playlist in Playlists.get_all(session):
if playlist.id == source_playlist_id:
continue
@ -1005,7 +1002,7 @@ class Window(QMainWindow, Ui_MainWindow):
def new_from_template(self) -> None:
"""Create new playlist from template"""
with Session() as session:
with db.Session() as session:
templates = Playlists.get_all_templates(session)
dlg = SelectPlaylistDialog(self, playlists=templates, session=session)
dlg.exec()
@ -1031,7 +1028,7 @@ class Window(QMainWindow, Ui_MainWindow):
def open_playlist(self) -> None:
"""Open existing playlist"""
with Session() as session:
with db.Session() as session:
playlists = Playlists.get_closed(session)
dlg = SelectPlaylistDialog(self, playlists=playlists, session=session)
dlg.exec()
@ -1193,7 +1190,7 @@ class Window(QMainWindow, Ui_MainWindow):
Rename current playlist
"""
with Session() as session:
with db.Session() as session:
playlist_id = self.active_tab().playlist_id
playlist = session.get(Playlists, playlist_id)
if playlist:
@ -1242,7 +1239,7 @@ class Window(QMainWindow, Ui_MainWindow):
def save_as_template(self) -> None:
"""Save current playlist as template"""
with Session() as session:
with db.Session() as session:
template_names = [a.name for a in Playlists.get_all_templates(session)]
while True:
@ -1304,7 +1301,7 @@ class Window(QMainWindow, Ui_MainWindow):
def set_main_window_size(self) -> None:
"""Set size of window from database"""
with Session() as session:
with db.Session() as session:
settings = Settings.all_as_dict(session)
record = settings["mainwindow_x"]
x = record.f_int or 1
@ -1751,18 +1748,15 @@ if __name__ == "__main__":
# Run as required
if args.check_db:
log.debug("Updating database")
with Session() as session:
log.debug("Checking database")
with db.Session() as session:
check_db(session)
engine.dispose()
elif args.update_bitrates:
log.debug("Update bitrates")
with Session() as session:
with db.Session() as session:
update_bitrates(session)
engine.dispose()
else:
try:
Base.metadata.create_all(engine)
app = QApplication(sys.argv)
# PyQt6 defaults to a grey for labels
palette = app.palette()
@ -1780,7 +1774,6 @@ if __name__ == "__main__":
win = Window()
win.show()
status = app.exec()
engine.dispose()
sys.exit(status)
except Exception as exc:
if os.environ["MM_ENV"] == "PRODUCTION":

View File

@ -1,5 +1,14 @@
# Standard library imports
# Allow forward reference to PlaylistModel
from __future__ import annotations
# PyQt imports
# Third party imports
# App imports
from dbtables import db
import obsws_python as obs # type: ignore
import re
import datetime as dt
@ -25,7 +34,6 @@ from PyQt6.QtGui import (
from classes import track_sequence, MusicMusterSignals, PlaylistTrack
from config import Config
from dbconfig import scoped_session, Session
from helpers import (
file_is_unreadable,
get_embedded_time,
@ -129,7 +137,7 @@ class PlaylistModel(QAbstractTableModel):
self.signals.end_reset_model_signal.connect(self.end_reset_model)
self.signals.row_order_changed_signal.connect(self.row_order_changed)
with Session() as session:
with db.Session() as session:
# Ensure row numbers in playlist are contiguous
PlaylistRows.fixup_rownumbers(session, playlist_id)
# Populate self.playlist_rows
@ -165,7 +173,7 @@ class PlaylistModel(QAbstractTableModel):
"Header row already has track associated"
)
return
with Session() as session:
with db.Session() as session:
plr = session.get(PlaylistRows, prd.plrid)
if plr:
# Add track to PlaylistRows
@ -187,7 +195,7 @@ class PlaylistModel(QAbstractTableModel):
# Header row
if self.is_header_row(row):
# Check for specific header colouring
with Session() as session:
with db.Session() as session:
note_colour = NoteColours.get_colour(session, prd.note)
if note_colour:
return QBrush(QColor(note_colour))
@ -216,7 +224,7 @@ class PlaylistModel(QAbstractTableModel):
return QBrush(QColor(Config.COLOUR_BITRATE_OK))
if column == Col.NOTE.value:
if prd.note:
with Session() as session:
with db.Session() as session:
note_colour = NoteColours.get_colour(session, prd.note)
if note_colour:
return QBrush(QColor(note_colour))
@ -275,7 +283,7 @@ class PlaylistModel(QAbstractTableModel):
log.debug("Call OBS scene change")
self.obs_scene_change(row_number)
with Session() as session:
with db.Session() as session:
# Update Playdates in database
log.debug("update playdates")
Playdates(session, track_sequence.now.track_id)
@ -377,7 +385,7 @@ class PlaylistModel(QAbstractTableModel):
Delete from highest row back so that not yet deleted row numbers don't change.
"""
with Session() as session:
with db.Session() as session:
for row_number in sorted(row_numbers, reverse=True):
log.info(f"delete_rows(), {row_number=}")
super().beginRemoveRows(QModelIndex(), row_number, row_number)
@ -454,7 +462,7 @@ class PlaylistModel(QAbstractTableModel):
if playlist_id != self.playlist_id:
log.debug(f"end_reset_model: not us ({self.playlist_id=})")
return
with Session() as session:
with db.Session() as session:
self.refresh_data(session)
super().endResetModel()
self.reset_track_sequence_row_numbers()
@ -754,7 +762,7 @@ class PlaylistModel(QAbstractTableModel):
new_row_number = self._get_new_row_number(proposed_row_number)
with Session() as session:
with db.Session() as session:
super().beginInsertRows(QModelIndex(), new_row_number, new_row_number)
plr = PlaylistRows.insert_row(session, self.playlist_id, new_row_number)
@ -820,7 +828,7 @@ class PlaylistModel(QAbstractTableModel):
Mark row as unplayed
"""
with Session() as session:
with db.Session() as session:
for row_number in row_numbers:
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
if not plr:
@ -883,7 +891,7 @@ class PlaylistModel(QAbstractTableModel):
plrid = self.playlist_rows[oldrow].plrid
sqla_map.append({"plrid": plrid, "plr_rownum": newrow})
with Session() as session:
with db.Session() as session:
PlaylistRows.update_plr_rownumbers(session, self.playlist_id, sqla_map)
# Update playlist_rows
self.refresh_data(session)
@ -912,7 +920,7 @@ class PlaylistModel(QAbstractTableModel):
# Prepare destination playlist for a reset
self.signals.begin_reset_model_signal.emit(to_playlist_id)
with Session() as session:
with db.Session() as session:
# Make room in destination playlist
max_destination_row_number = PlaylistRows.get_last_used_row(
session, to_playlist_id
@ -962,7 +970,7 @@ class PlaylistModel(QAbstractTableModel):
log.info(f"move_track_add_note({new_row_number=}, {existing_prd=}, {note=}")
if note:
with Session() as session:
with db.Session() as session:
plr = session.get(PlaylistRows, existing_prd.plrid)
if plr:
if plr.note:
@ -1050,7 +1058,7 @@ class PlaylistModel(QAbstractTableModel):
# Update display
self.invalidate_row(track_sequence.previous.plr_rownum)
def refresh_data(self, session: scoped_session):
def refresh_data(self, session: db.session):
"""Populate dicts for data calls"""
# Populate self.playlist_rows with playlist data
@ -1071,7 +1079,7 @@ class PlaylistModel(QAbstractTableModel):
log.info(f"remove_track({row_number=})")
with Session() as session:
with db.Session() as session:
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
if plr:
plr.track_id = None
@ -1085,7 +1093,7 @@ class PlaylistModel(QAbstractTableModel):
track_id = self.playlist_rows[row_number].track_id
if track_id:
with Session() as session:
with db.Session() as session:
track = session.get(Tracks, track_id)
set_track_metadata(track)
self.refresh_row(session, row_number)
@ -1101,7 +1109,7 @@ class PlaylistModel(QAbstractTableModel):
# Check the track_sequence next, now and previous plrs and
# update the row number
with Session() as session:
with db.Session() as session:
if track_sequence.next.plr_rownum:
next_plr = session.get(PlaylistRows, track_sequence.next.plr_id)
if next_plr:
@ -1206,7 +1214,7 @@ class PlaylistModel(QAbstractTableModel):
return
# Update track_sequence
with Session() as session:
with db.Session() as session:
track_sequence.next = PlaylistTrack()
try:
plrid = self.playlist_rows[row_number].plrid
@ -1246,7 +1254,7 @@ class PlaylistModel(QAbstractTableModel):
row_number = index.row()
column = index.column()
with Session() as session:
with db.Session() as session:
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
if not plr:
print(
@ -1455,7 +1463,7 @@ class PlaylistProxyModel(QSortFilterProxyModel):
if self.source_model.played_tracks_hidden:
if self.source_model.is_played_row(source_row):
# Don't hide current or next track
with Session() as session:
with db.Session() as session:
if track_sequence.next.plr_id:
next_plr = session.get(PlaylistRows, track_sequence.next.plr_id)
if (

View File

@ -1,8 +1,9 @@
# Standard library imports
from typing import Callable, cast, List, Optional, TYPE_CHECKING
import psutil
import time
from pprint import pprint
from typing import Callable, cast, List, Optional, TYPE_CHECKING
# PyQt imports
from PyQt6.QtCore import (
QEvent,
QModelIndex,
@ -30,10 +31,13 @@ from PyQt6.QtWidgets import (
QStyleOption,
)
from dbconfig import Session
from dialogs import TrackSelectDialog
# Third party imports
# App imports
from classes import MusicMusterSignals, track_sequence
from config import Config
from dbtables import db
from dialogs import TrackSelectDialog
from helpers import (
ask_yes_no,
ms_to_mmss,
@ -42,10 +46,9 @@ from helpers import (
)
from log import log
from models import Settings
from playlistmodel import PlaylistModel, PlaylistProxyModel
if TYPE_CHECKING:
from musicmuster import Window
from playlistmodel import PlaylistModel, PlaylistProxyModel
class EscapeDelegate(QStyledItemDelegate):
@ -335,7 +338,7 @@ class PlaylistTab(QTableView):
if model_row_number is None:
return
with Session() as session:
with db.Session() as session:
dlg = TrackSelectDialog(
session=session,
new_row_number=model_row_number,
@ -536,7 +539,7 @@ class PlaylistTab(QTableView):
# Resize rows if necessary
self.resizeRowsToContents()
with Session() as session:
with db.Session() as session:
attr_name = f"playlist_col_{column_number}_width"
record = Settings.get_int_settings(session, attr_name)
record.f_int = self.columnWidth(column_number)
@ -830,7 +833,7 @@ class PlaylistTab(QTableView):
return
# Last column is set to stretch so ignore it here
with Session() as session:
with db.Session() as session:
for column_number in range(header.count() - 1):
attr_name = f"playlist_col_{column_number}_width"
record = Settings.get_int_settings(session, attr_name)

20
poetry.lock generated
View File

@ -12,6 +12,24 @@ files = [
{file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"},
]
[[package]]
name = "alchemical"
version = "1.0.1"
description = "Modern SQLAlchemy simplified"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "alchemical-1.0.1-py3-none-any.whl", hash = "sha256:79a98d459ed4f8afa8ed44b21d4d2ccf3586c76d73f53f647a9338aaba2bb33c"},
{file = "alchemical-1.0.1.tar.gz", hash = "sha256:cff882cfef9533a56c53aa6bb38d5fa19939ea283226017c7c9369b73422200a"},
]
[package.dependencies]
sqlalchemy = ">=1.4.24"
[package.extras]
docs = ["sphinx"]
[[package]]
name = "alembic"
version = "1.13.1"
@ -2273,4 +2291,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = "^3.9"
content-hash = "500cefc31e30cba9ae917cc51b7407961d69825d1fcae53515ed1fa12f4ab171"
content-hash = "f4fb2696ae984283c4c0d7816ba7cbd7be714695d6eb3c84b5da62b3809f9c82"

View File

@ -27,6 +27,7 @@ pyqt6-webengine = "^6.5.0"
pygame = "^2.4.0"
pyqtgraph = "^0.13.3"
colorlog = "^6.8.0"
alchemical = "^1.0.1"
[tool.poetry.dev-dependencies]
ipdb = "^0.13.9"