Migrate to Alchemical
This commit is contained in:
parent
6890e0d0c2
commit
9d44642fea
@ -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
|
||||
|
||||
|
||||
@ -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
187
app/dbtables.py
Normal 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)
|
||||
@ -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,
|
||||
|
||||
189
app/models.py
189
app/models.py
@ -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 (
|
||||
|
||||
@ -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":
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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
20
poetry.lock
generated
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user