From 907861ea4828b5cc6657c2d2a6bb11996cc61bc2 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Wed, 2 Mar 2022 09:13:43 +0000 Subject: [PATCH] Rebase dev onto v2_id --- app/models.py | 172 ++---------- app/models.py.orig | 673 --------------------------------------------- app/musicmuster.py | 2 +- poetry.lock | 2 +- pyproject.toml | 1 + 5 files changed, 29 insertions(+), 821 deletions(-) delete mode 100644 app/models.py.orig diff --git a/app/models.py b/app/models.py index 62579f6..9d8462a 100644 --- a/app/models.py +++ b/app/models.py @@ -21,8 +21,8 @@ from sqlalchemy.exc import IntegrityError from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound from sqlalchemy.orm import relationship, sessionmaker, scoped_session -from config import Config -from log import DEBUG, ERROR +from app.config import Config +from app.log import DEBUG, ERROR # Create session at the global level as per # https://docs.sqlalchemy.org/en/13/orm/session_basics.html @@ -87,10 +87,10 @@ class NoteColours(Base): """ for rec in ( - session.query(NoteColours) - .filter(NoteColours.enabled.is_(True)) - .order_by(NoteColours.order) - .all() + session.query(NoteColours) + .filter(NoteColours.enabled.is_(True)) + .order_by(NoteColours.order) + .all() ): if rec.is_regex: flags = re.UNICODE @@ -164,19 +164,16 @@ class Playdates(Base): track_id = Column(Integer, ForeignKey('tracks.id')) tracks = relationship("Tracks", back_populates="playdates") - @classmethod - def add_playdate(cls, session, track): + def __init__(self, session, track): """Record that track was played""" DEBUG(f"add_playdate(track={track})") - pd = Playdates() - pd.lastplayed = datetime.now() - pd.track_id = track.id - session.add(pd) - track.update_lastplayed(session) - session.commit() - return pd + self.lastplayed = datetime.now() + self.track_id = track.id + track.update_lastplayed(session) + session.add(self) + session.commit() @staticmethod def last_played(session, track_id): @@ -204,45 +201,7 @@ class Playdates(Base): class Playlists(Base): """ - Usage: - - pl = session.query(Playlists).filter(Playlists.id == 1).one() - - pl - - - pl.tracks - [<__main__.PlaylistTracks at 0x7fcd20181c18>, - <__main__.PlaylistTracks at 0x7fcd20181c88>, - <__main__.PlaylistTracks at 0x7fcd20181be0>, - <__main__.PlaylistTracks at 0x7fcd20181c50>] - - [a.tracks for a in pl.tracks] - [ - - glue.track_id = tr.id - - pl.tracks.append(glue) - - session.commit() - - [a.tracks for a in pl.tracks] - [" - ) - - @classmethod - def get_all(cls, session): - """Return all records""" - - return session.query(cls).all() - - @classmethod - def get_by_id(cls, session, note_id): - """Return record identified by id, or None if not found""" - - return session.query(NoteColours).filter( - NoteColours.id == note_id).first() - - @staticmethod - def get_colour(session, text): - """ - Parse text and return colour string if matched, else None - """ - - for rec in ( - session.query(NoteColours) - .filter(NoteColours.enabled.is_(True)) - .order_by(NoteColours.order) - .all() - ): - if rec.is_regex: - flags = re.UNICODE - if not rec.is_casesensitive: - flags |= re.IGNORECASE - p = re.compile(rec.substring, flags) - if p.match(text): - return rec.colour - else: - if rec.is_casesensitive: - if rec.substring in text: - return rec.colour - else: - if rec.substring.lower() in text.lower(): - return rec.colour - - return None - - -class Notes(Base): - __tablename__ = 'notes' - - id = Column(Integer, primary_key=True, autoincrement=True) - playlist_id = Column(Integer, ForeignKey('playlists.id')) - playlist = relationship("Playlists", back_populates="notes") - row = Column(Integer, nullable=False) - note = Column(String(256), index=False) - - def __repr__(self): - return ( - f"" - ) - - @classmethod - def add_note(cls, session, playlist_id, row, text): - """Add note""" - - DEBUG(f"add_note(playlist_id={playlist_id}, row={row}, text={text})") - note = Notes() - note.playlist_id = playlist_id - note.row = row - note.note = text - session.add(note) - session.commit() - - return note - - @staticmethod - def delete_note(session, note_id): - """Delete note""" - - DEBUG(f"delete_note(id={note_id}") - - session.query(Notes).filter(Notes.id == note_id).delete() - session.commit() - - @classmethod - def update_note(cls, session, note_id, row, text=None): - """ - Update note details. If text=None, don't change text. - """ - - DEBUG(f"Notes.update_note(id={note_id}, row={row}, text={text})") - - note = session.query(Notes).filter(Notes.id == note_id).one() - note.row = row - if text: - note.note = text - session.commit() - - return note - - -class Playdates(Base): - __tablename__ = 'playdates' - - id = Column(Integer, primary_key=True, autoincrement=True) - lastplayed = Column(DateTime, index=True, default=None) - track_id = Column(Integer, ForeignKey('tracks.id')) - tracks = relationship("Tracks", back_populates="playdates") - - @classmethod - def add_playdate(cls, session, track): - """Record that track was played""" - - DEBUG(f"add_playdate(track={track})") - pd = Playdates() - pd.lastplayed = datetime.now() - pd.track_id = track.id - session.add(pd) - track.update_lastplayed(session) - session.commit() - - return pd - - @staticmethod - def last_played(session, track_id): - """Return datetime track last played or None""" - - last_played = session.query(Playdates.lastplayed).filter( - (Playdates.track_id == track_id) - ).order_by(Playdates.lastplayed.desc()).first() - if last_played: - return last_played[0] - else: - return None - - @staticmethod - def remove_track(session, track_id): - """ - Remove all records of track_id - """ - - session.query(Playdates).filter( - Playdates.track_id == track_id, - ).delete() - session.commit() - - -class Playlists(Base): - """ - Usage: - - pl = session.query(Playlists).filter(Playlists.id == 1).one() - - pl - - - pl.tracks - [<__main__.PlaylistTracks at 0x7fcd20181c18>, - <__main__.PlaylistTracks at 0x7fcd20181c88>, - <__main__.PlaylistTracks at 0x7fcd20181be0>, - <__main__.PlaylistTracks at 0x7fcd20181c50>] - - [a.tracks for a in pl.tracks] - [ - - glue.track_id = tr.id - - pl.tracks.append(glue) - - session.commit() - - [a.tracks for a in pl.tracks] - [" - - def close(self, session): - """Record playlist as no longer loaded""" - - self.loaded = False - session.add(self) - session.commit() - - @classmethod - def get_all_closed_playlists(cls, session): - """Returns a list of all playlists not currently open""" - - return ( - session.query(cls) - .filter( - (cls.loaded == False) | # noqa E712 - (cls.loaded.is_(None)) - ) - .order_by(cls.last_used.desc()) - ).all() - - @classmethod - def get_all_playlists(cls, session): - """Returns a list of all playlists""" - - return session.query(cls).all() - - @classmethod - def get_last_used(cls, session): - """ - Return a list of playlists marked "loaded", ordered by loaded date. - """ - - return ( - session.query(cls) - .filter(cls.loaded == True) # noqa E712 - .order_by(cls.last_used.desc()) - ).all() - - @classmethod - def get_playlist_by_id(cls, session, playlist_id): - - return (session.query(cls).filter(cls.id == playlist_id)).one() - - @classmethod - def new(cls, session, name): - DEBUG(f"Playlists.new(name={name})") - playlist = cls() - playlist.name = name - session.add(playlist) - session.commit() - - return playlist - - def open(self, session): - """Mark playlist as loaded and used now""" - - self.loaded = True - self.last_used = datetime.now() - session.add(self) - session.commit() - - -class PlaylistTracks(Base): - __tablename__ = 'playlisttracks' - - id = Column(Integer, primary_key=True, autoincrement=True) - playlist_id = Column(Integer, ForeignKey('playlists.id'), primary_key=True) - track_id = Column(Integer, ForeignKey('tracks.id'), primary_key=True) - row = Column(Integer, nullable=False) - tracks = relationship("Tracks", back_populates="playlists") - playlists = relationship("Playlists", back_populates="tracks") - - @staticmethod - def add_track(session, playlist_id, track_id, row): - DEBUG( - f"PlaylistTracks.add_track(playlist_id={playlist_id}, " - f"track_id={track_id}, row={row})" - ) - plt = PlaylistTracks() - plt.playlist_id = playlist_id, - plt.track_id = track_id, - plt.row = row - session.add(plt) - session.commit() - - @staticmethod - def get_track_playlists(session, track_id): - """Return all PlaylistTracks objects with this track_id""" - - return session.query(PlaylistTracks).filter( - PlaylistTracks.track_id == track_id).all() - - @staticmethod - def move_track(session, from_playlist_id, row, to_playlist_id): - DEBUG( - "PlaylistTracks.move_tracks(from_playlist_id=" - f"{from_playlist_id}, row={row}, " - f"to_playlist_id={to_playlist_id})" - ) - max_row = session.query(func.max(PlaylistTracks.row)).filter( - PlaylistTracks.playlist_id == to_playlist_id).scalar() - if max_row is None: - # Destination playlist is empty; use row 0 - new_row = 0 - else: - # Destination playlist has tracks; add to end - new_row = max_row + 1 - try: - record = session.query(PlaylistTracks).filter( - PlaylistTracks.playlist_id == from_playlist_id, - PlaylistTracks.row == row).one() - except NoResultFound: - # Issue #38? - ERROR( - f"No rows matched in query: " - f"PlaylistTracks.playlist_id == {from_playlist_id}, " - f"PlaylistTracks.row == {row}" - ) - return - record.playlist_id = to_playlist_id - record.row = new_row - session.commit() - - @staticmethod - def new_row(session, playlist_id): - """ - Return row number > the largest existing row number - - If there are no existing rows, return 0 (ie, first row number) - """ - - last_row = session.query(func.max(PlaylistTracks.row)).one()[0] - if last_row: - return last_row + 1 - else: - return 0 - - @staticmethod - def remove_all_tracks(session, playlist_id): - """ - Remove all tracks from passed playlist_id - """ - - session.query(PlaylistTracks).filter( - PlaylistTracks.playlist_id == playlist_id, - ).delete() - session.commit() - - @staticmethod - def remove_track(session, playlist_id, row): - DEBUG( - f"PlaylistTracks.remove_track(playlist_id={playlist_id}, " - f"row={row})" - ) - session.query(PlaylistTracks).filter( - PlaylistTracks.playlist_id == playlist_id, - PlaylistTracks.row == row - ).delete() - session.commit() - - @staticmethod - def update_row_track(session, playlist_id, row, track_id): - DEBUG( - f"PlaylistTracks.update_track_row(playlist_id={playlist_id}, " - f"row={row}, track_id={track_id})" - ) - - try: - plt = session.query(PlaylistTracks).filter( - PlaylistTracks.playlist_id == playlist_id, - PlaylistTracks.row == row - ).one() - except MultipleResultsFound: - ERROR( - f"Multiple rows matched in query: " - f"PlaylistTracks.playlist_id == {playlist_id}, " - f"PlaylistTracks.row == {row}" - ) - return - except NoResultFound: - ERROR( - f"No rows matched in query: " - f"PlaylistTracks.playlist_id == {playlist_id}, " - f"PlaylistTracks.row == {row}" - ) - return - - plt.track_id = track_id - session.commit() - - -class Settings(Base): - __tablename__ = 'settings' - - id = Column(Integer, primary_key=True, autoincrement=True) - name = Column(String(32), nullable=False, unique=True) - f_datetime = Column(DateTime, default=None, nullable=True) - f_int = Column(Integer, default=None, nullable=True) - f_string = Column(String(128), default=None, nullable=True) - - @classmethod - def get_int(cls, session, name): - try: - int_setting = session.query(cls).filter( - cls.name == name).one() - except NoResultFound: - int_setting = Settings() - int_setting.name = name - int_setting.f_int = None - session.add(int_setting) - session.commit() - return int_setting - - def update(self, session, data): - for key, value in data.items(): - assert hasattr(self, key) - setattr(self, key, value) - session.commit() - - -class Tracks(Base): - __tablename__ = 'tracks' - - id = Column(Integer, primary_key=True, autoincrement=True) - title = Column(String(256), index=True) - artist = Column(String(256), index=True) - duration = Column(Integer, index=True) - start_gap = Column(Integer, index=False) - fade_at = Column(Integer, index=False) - silence_at = Column(Integer, index=False) - path = Column(String(2048), index=False, nullable=False) - mtime = Column(Float, index=True) - lastplayed = Column(DateTime, index=True, default=None) - playlists = relationship("PlaylistTracks", back_populates="tracks") - playdates = relationship("Playdates", back_populates="tracks") - - def __repr__(self): - return ( - f"" - ) - - # Not currently used 1 June 2021 - # @staticmethod - # def get_note(session, id): - # return session.query(Notes).filter(Notes.id == id).one() - - @staticmethod - def get_all_paths(session): - """Return a list of paths of all tracks""" - - return [a[0] for a in session.query(Tracks.path).all()] - - @classmethod - def get_all_tracks(cls, session): - "Return a list of all tracks" - - return session.query(Tracks).all() - - @classmethod - def get_or_create(cls, session, path): - DEBUG(f"Tracks.get_or_create(path={path})") - try: - track = session.query(cls).filter(cls.path == path).one() - except NoResultFound: - track = Tracks() - track.path = path - session.add(track) - return track - -# @staticmethod -# def get_duration(session, id): -# try: -# return session.query( -# Tracks.duration).filter(Tracks.id == id).one()[0] -# except NoResultFound: -# ERROR(f"Can't find track id {id}") -# return None - - @classmethod - def get_from_filename(cls, session, filename): - """ - Return track if one and only one track in database has passed - filename (ie, basename of path). Return None if zero or more - than one track matches. - """ - - DEBUG(f"Tracks.get_track_from_filename({filename=})") - try: - track = session.query(Tracks).filter(Tracks.path.ilike( - f'%{os.path.sep}{filename}')).one() - return track - except (NoResultFound, MultipleResultsFound): - return None - - @classmethod - def get_from_id(cls, session, id): - return session.query(Tracks).filter( - Tracks.id == id).one() - - @classmethod - def get_from_path(cls, session, path): - """ - Return track with passee path, or None. - """ - - DEBUG(f"Tracks.get_track_from_path({path=})") - - return session.query(Tracks).filter(Tracks.path == path).first() - -# @staticmethod -# def get_path(session, track_id): -# "Return path of passed track_id, or None" -# -# try: -# return session.query(Tracks.path).filter( -# Tracks.id == track_id).one()[0] -# except NoResultFound: -# ERROR(f"Can't find track id {track_id}") -# return None - - @classmethod - def get_by_id(cls, session, track_id): - """Return track or None""" - - try: - DEBUG(f"Tracks.get_track(track_id={track_id})") - track = session.query(Tracks).filter(Tracks.id == track_id).one() - return track - except NoResultFound: - ERROR(f"get_track({track_id}): not found") - return None - - @staticmethod - def remove_by_path(session, path): - "Remove track with passed path from database" - - DEBUG(f"Tracks.remove_path({path=})") - - try: - session.query(Tracks).filter(Tracks.path == path).delete() - session.commit() - except IntegrityError as exception: - ERROR(f"Can't remove track with {path=} ({exception=})") - -# @staticmethod -# def search(session, title=None, artist=None, duration=None): -# """ -# Return any tracks matching passed criteria -# """ -# -# DEBUG( -# f"Tracks.search({title=}, {artist=}), {duration=})" -# ) -# -# if not title and not artist and not duration: -# return None -# -# q = session.query(Tracks).filter(False) -# if title: -# q = q.filter(Tracks.title == title) -# if artist: -# q = q.filter(Tracks.artist == artist) -# if duration: -# q = q.filter(Tracks.duration == duration) -# -# return q.all() - - @classmethod - def search_artists(cls, session, text): - - return ( - session.query(Tracks) - .filter(Tracks.artist.ilike(f"%{text}%")) - .order_by(Tracks.title) - ).all() - - @classmethod - def search_titles(cls, session, text): - return ( - session.query(Tracks) - .filter(Tracks.title.ilike(f"%{text}%")) - .order_by(Tracks.title) - ).all() - - def update_lastplayed(self, session): - self.lastplayed = datetime.now() - session.add(self) - session.commit() - - def update_artist(self, session, artist): - self.artist = artist - session.add(self) - session.commit() - - def update_title(self, session, title): - self.title = title - session.add(self) - session.commit() - - def update_path(self, newpath): - self.path = newpath diff --git a/app/musicmuster.py b/app/musicmuster.py index 7686bac..f122344 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -529,7 +529,7 @@ class Window(QMainWindow, Ui_MainWindow): QColor(Config.COLOUR_NEXT_TAB)) # Tell database to record it as played - Playdates.add_playdate(session, self.current_track) + Playdates(session, self.current_track) self.disable_play_next_controls() self.update_headers() diff --git a/poetry.lock b/poetry.lock index 89a75f1..d444059 100644 --- a/poetry.lock +++ b/poetry.lock @@ -461,7 +461,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" diff --git a/pyproject.toml b/pyproject.toml index 7dfbf56..956a0b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ mypy = "^0.931" pytest = "^7.0.0" ipdb = "^0.13.9" mypy = "^0.931" +sqlalchemy-stubs = "^0.4" [build-system] requires = ["poetry-core>=1.0.0"]