Compare commits

...

4 Commits

Author SHA1 Message Date
Keith Edmunds
d471082e3f Clean up dependencies, update them, add vulture settings 2023-01-20 22:01:52 +00:00
Keith Edmunds
e77c05b908 Remove unused functions 2023-01-20 22:01:05 +00:00
Keith Edmunds
ffa3015ac3 Fix move existing track when adding duplicate with note
Fixes #161
2023-01-20 22:00:47 +00:00
Keith Edmunds
f8dcc69a55 Python typing fixups 2023-01-20 21:59:40 +00:00
5 changed files with 65 additions and 106 deletions

View File

@ -65,7 +65,6 @@ class Config(object):
MAX_MISSING_FILES_TO_REPORT = 10 MAX_MISSING_FILES_TO_REPORT = 10
MILLISECOND_SIGFIGS = 0 MILLISECOND_SIGFIGS = 0
MYSQL_CONNECT = os.environ.get('MYSQL_CONNECT') or "mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_v2" # noqa E501 MYSQL_CONNECT = os.environ.get('MYSQL_CONNECT') or "mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_v2" # noqa E501
NORMALISE_ON_IMPORT = True
NOTE_TIME_FORMAT = "%H:%M:%S" NOTE_TIME_FORMAT = "%H:%M:%S"
ROOT = os.environ.get('ROOT') or "/home/kae/music" ROOT = os.environ.get('ROOT') or "/home/kae/music"
IMPORT_DESTINATION = os.path.join(ROOT, "Singles") IMPORT_DESTINATION = os.path.join(ROOT, "Singles")

View File

@ -4,7 +4,7 @@ import os.path
import re import re
import stackprinter # type: ignore import stackprinter # type: ignore
from dbconfig import Session from dbconfig import Session, scoped_session
from datetime import datetime from datetime import datetime
from typing import List, Optional from typing import List, Optional
@ -61,7 +61,8 @@ class Carts(Base):
f"name={self.name}, path={self.path}>" f"name={self.name}, path={self.path}>"
) )
def __init__(self, session: Session, cart_number: int, name: str = None, def __init__(self, session: scoped_session, cart_number: int,
name: Optional[str] = None,
duration: int = None, path: str = None, duration: int = None, path: str = None,
enabled: bool = True) -> None: enabled: bool = True) -> None:
"""Create new cart""" """Create new cart"""
@ -94,7 +95,7 @@ class NoteColours(Base):
) )
@staticmethod @staticmethod
def get_colour(session: Session, text: str) -> Optional[str]: def get_colour(session: scoped_session, text: str) -> Optional[str]:
""" """
Parse text and return colour string if matched, else None Parse text and return colour string if matched, else None
""" """
@ -139,7 +140,7 @@ class Playdates(Base):
f"lastplayed={self.lastplayed}>" f"lastplayed={self.lastplayed}>"
) )
def __init__(self, session: Session, track_id: int) -> None: def __init__(self, session: scoped_session, track_id: int) -> None:
"""Record that track was played""" """Record that track was played"""
self.lastplayed = datetime.now() self.lastplayed = datetime.now()
@ -148,7 +149,7 @@ class Playdates(Base):
session.commit() session.commit()
@staticmethod @staticmethod
def last_played(session: Session, track_id: int) -> Optional[datetime]: def last_played(session: scoped_session, track_id: int) -> Optional[datetime]:
"""Return datetime track last played or None""" """Return datetime track last played or None"""
last_played = session.execute( last_played = session.execute(
@ -164,7 +165,7 @@ class Playdates(Base):
return None return None
@staticmethod @staticmethod
def played_after(session: Session, since: datetime) -> List["Playdates"]: def played_after(session: scoped_session, since: datetime) -> List["Playdates"]:
"""Return a list of Playdates objects since passed time""" """Return a list of Playdates objects since passed time"""
return ( return (
@ -206,12 +207,12 @@ class Playlists(Base):
f"is_templatee={self.is_template}>" f"is_templatee={self.is_template}>"
) )
def __init__(self, session: Session, name: str) -> None: def __init__(self, session: scoped_session, name: str) -> None:
self.name = name self.name = name
session.add(self) session.add(self)
session.commit() session.commit()
def close(self, session: Session) -> None: def close(self, session: scoped_session) -> None:
"""Mark playlist as unloaded""" """Mark playlist as unloaded"""
closed_idx = self.tab closed_idx = self.tab
@ -227,7 +228,7 @@ class Playlists(Base):
@classmethod @classmethod
def create_playlist_from_template(cls, def create_playlist_from_template(cls,
session: Session, session: scoped_session,
template: "Playlists", template: "Playlists",
playlist_name: str) \ playlist_name: str) \
-> "Playlists": -> "Playlists":
@ -239,7 +240,7 @@ class Playlists(Base):
return playlist return playlist
@classmethod @classmethod
def get_all(cls, session: Session) -> List["Playlists"]: def get_all(cls, session: scoped_session) -> List["Playlists"]:
"""Returns a list of all playlists ordered by last use""" """Returns a list of all playlists ordered by last use"""
return ( return (
@ -253,7 +254,7 @@ class Playlists(Base):
) )
@classmethod @classmethod
def get_all_templates(cls, session: Session) -> List["Playlists"]: def get_all_templates(cls, session: scoped_session) -> List["Playlists"]:
"""Returns a list of all templates ordered by name""" """Returns a list of all templates ordered by name"""
return ( return (
@ -267,7 +268,7 @@ class Playlists(Base):
) )
@classmethod @classmethod
def get_closed(cls, session: Session) -> List["Playlists"]: def get_closed(cls, session: scoped_session) -> List["Playlists"]:
"""Returns a list of all closed playlists ordered by last use""" """Returns a list of all closed playlists ordered by last use"""
return ( return (
@ -284,7 +285,7 @@ class Playlists(Base):
) )
@classmethod @classmethod
def get_open(cls, session: Session) -> List[Optional["Playlists"]]: def get_open(cls, session: scoped_session) -> List[Optional["Playlists"]]:
""" """
Return a list of loaded playlists ordered by tab order. Return a list of loaded playlists ordered by tab order.
""" """
@ -299,14 +300,14 @@ class Playlists(Base):
.all() .all()
) )
def mark_open(self, session: Session, tab_index: int) -> None: def mark_open(self, session: scoped_session, tab_index: int) -> None:
"""Mark playlist as loaded and used now""" """Mark playlist as loaded and used now"""
self.tab = tab_index self.tab = tab_index
self.last_used = datetime.now() self.last_used = datetime.now()
@staticmethod @staticmethod
def move_tab(session: Session, frm: int, to: int) -> None: def move_tab(session: scoped_session, frm: int, to: int) -> None:
"""Move tabs""" """Move tabs"""
row_frm = session.execute( row_frm = session.execute(
@ -326,7 +327,7 @@ class Playlists(Base):
row_frm.tab = to row_frm.tab = to
@staticmethod @staticmethod
def save_as_template(session: Session, def save_as_template(session: scoped_session,
playlist_id: int, template_name: str) -> None: playlist_id: int, template_name: str) -> None:
"""Save passed playlist as new template""" """Save passed playlist as new template"""
@ -357,7 +358,7 @@ class PlaylistRows(Base):
) )
def __init__(self, def __init__(self,
session: Session, session: scoped_session,
playlist_id: int, playlist_id: int,
track_id: int, track_id: int,
row_number: int, row_number: int,
@ -372,8 +373,17 @@ class PlaylistRows(Base):
session.add(self) session.add(self)
session.flush() session.flush()
def append_note(self, extra_note: str) -> None:
"""Append passed note to any existing note"""
current_note = self.note
if current_note:
self.note = current_note + '\n' + extra_note
else:
self.note = extra_note
@staticmethod @staticmethod
def copy_playlist(session: Session, def copy_playlist(session: scoped_session,
src_id: int, src_id: int,
dst_id: int) -> None: dst_id: int) -> None:
"""Copy playlist entries""" """Copy playlist entries"""
@ -388,7 +398,7 @@ class PlaylistRows(Base):
plr.note) plr.note)
@staticmethod @staticmethod
def delete_plrids_not_in_list(session: Session, playlist_id: int, def delete_plrids_not_in_list(session: scoped_session, playlist_id: int,
plrids: List["PlaylistRows"]) -> None: plrids: List["PlaylistRows"]) -> None:
""" """
Delete rows in given playlist that have a higher row number Delete rows in given playlist that have a higher row number
@ -406,7 +416,7 @@ class PlaylistRows(Base):
session.commit() session.commit()
@staticmethod @staticmethod
def fixup_rownumbers(session: Session, playlist_id: int) -> None: def fixup_rownumbers(session: scoped_session, playlist_id: int) -> None:
""" """
Ensure the row numbers for passed playlist have no gaps Ensure the row numbers for passed playlist have no gaps
""" """
@ -424,7 +434,7 @@ class PlaylistRows(Base):
session.commit() session.commit()
@staticmethod @staticmethod
def get_track_plr(session: Session, track_id: int, def get_track_plr(session: scoped_session, track_id: int,
playlist_id: int) -> Optional["PlaylistRows"]: playlist_id: int) -> Optional["PlaylistRows"]:
"""Return first matching PlaylistRows object or None""" """Return first matching PlaylistRows object or None"""
@ -438,7 +448,7 @@ class PlaylistRows(Base):
).first() ).first()
@staticmethod @staticmethod
def get_last_used_row(session: Session, playlist_id: int) -> Optional[int]: def get_last_used_row(session: scoped_session, playlist_id: int) -> Optional[int]:
"""Return the last used row for playlist, or None if no rows""" """Return the last used row for playlist, or None if no rows"""
return session.execute( return session.execute(
@ -447,7 +457,7 @@ class PlaylistRows(Base):
).scalar_one() ).scalar_one()
@classmethod @classmethod
def get_played_rows(cls, session: Session, def get_played_rows(cls, session: scoped_session,
playlist_id: int) -> List[int]: playlist_id: int) -> List[int]:
""" """
For passed playlist, return a list of rows that For passed playlist, return a list of rows that
@ -466,7 +476,7 @@ class PlaylistRows(Base):
return plrs return plrs
@classmethod @classmethod
def get_rows_with_tracks(cls, session: Session, def get_rows_with_tracks(cls, session: scoped_session,
playlist_id: int) -> List[int]: playlist_id: int) -> List[int]:
""" """
For passed playlist, return a list of rows that For passed playlist, return a list of rows that
@ -485,8 +495,8 @@ class PlaylistRows(Base):
return plrs return plrs
@classmethod @classmethod
def get_unplayed_rows(cls, session: Session, def get_unplayed_rows(cls, session: scoped_session,
playlist_id: int) -> List["PlaylistRows]: playlist_id: int) -> List["PlaylistRows"]:
""" """
For passed playlist, return a list of playlist rows that For passed playlist, return a list of playlist rows that
have not been played. have not been played.
@ -505,7 +515,7 @@ class PlaylistRows(Base):
return plrs return plrs
@staticmethod @staticmethod
def move_rows_down(session: Session, playlist_id: int, starting_row: int, def move_rows_down(session: scoped_session, playlist_id: int, starting_row: int,
move_by: int) -> None: move_by: int) -> None:
""" """
Create space to insert move_by additional rows by incremented row Create space to insert move_by additional rows by incremented row
@ -522,7 +532,7 @@ class PlaylistRows(Base):
) )
@staticmethod @staticmethod
def indexed_by_id(session: Session, plr_ids: List[int]) -> dict: def indexed_by_id(session: scoped_session, plr_ids: List[int]) -> dict:
""" """
Return a dictionary of playlist_rows indexed by their plr id from Return a dictionary of playlist_rows indexed by their plr id from
the passed plr_id list. the passed plr_id list.
@ -558,7 +568,7 @@ class Settings(Base):
return f"<Settings(id={self.id}, name={self.name}, {value=}>" return f"<Settings(id={self.id}, name={self.name}, {value=}>"
@classmethod @classmethod
def get_int_settings(cls, session: Session, name: str) -> "Settings": def get_int_settings(cls, session: scoped_session, name: str) -> "Settings":
"""Get setting for an integer or return new setting record""" """Get setting for an integer or return new setting record"""
int_setting: Settings int_setting: Settings
@ -577,7 +587,7 @@ class Settings(Base):
return int_setting return int_setting
def update(self, session: Session, data: "Settings"): def update(self, session: scoped_session, data: "Settings"):
for key, value in data.items(): for key, value in data.items():
assert hasattr(self, key) assert hasattr(self, key)
setattr(self, key, value) setattr(self, key, value)
@ -609,7 +619,7 @@ class Tracks(Base):
def __init__( def __init__(
self, self,
session: Session, session: scoped_session,
path: str, path: str,
title: Optional[str] = None, title: Optional[str] = None,
artist: Optional[str] = None, artist: Optional[str] = None,
@ -640,7 +650,7 @@ class Tracks(Base):
return session.execute(select(cls)).scalars().all() return session.execute(select(cls)).scalars().all()
@classmethod @classmethod
def get_by_path(cls, session: Session, path: str) -> "Tracks": def get_by_path(cls, session: scoped_session, path: str) -> "Tracks":
""" """
Return track with passed path, or None. Return track with passed path, or None.
""" """
@ -656,7 +666,7 @@ class Tracks(Base):
return None return None
@classmethod @classmethod
def search_artists(cls, session: Session, text: str) -> List["Tracks"]: def search_artists(cls, session: scoped_session, text: str) -> List["Tracks"]:
"""Search case-insenstively for artists containing str""" """Search case-insenstively for artists containing str"""
return ( return (
@ -670,7 +680,7 @@ class Tracks(Base):
) )
@classmethod @classmethod
def search_titles(cls, session: Session, text: str) -> List["Tracks"]: def search_titles(cls, session: scoped_session, text: str) -> List["Tracks"]:
"""Search case-insenstively for titles containing str""" """Search case-insenstively for titles containing str"""
return ( return (
session.execute( session.execute(

View File

@ -554,7 +554,8 @@ class PlaylistTab(QTableWidget):
return [self._get_playlistrow_id(a) for a in self._get_selected_rows()] return [self._get_playlistrow_id(a) for a in self._get_selected_rows()]
def get_selected_playlistrows(self, session: scoped_session) -> Optional[List]: def get_selected_playlistrows(self,
session: scoped_session) -> Optional[List]:
""" """
Return a list of PlaylistRows of the selected rows Return a list of PlaylistRows of the selected rows
""" """
@ -695,7 +696,11 @@ class PlaylistTab(QTableWidget):
if existing_plr and ask_yes_no("Duplicate row", if existing_plr and ask_yes_no("Duplicate row",
"Track already in playlist. " "Track already in playlist. "
"Move to new location?"): "Move to new location?"):
# Yes it is and we shoudl reuse it # Yes it is and we should reuse it
# If we've been passed a note, we need to add that to the
# existing track
if note:
existing_plr.append_note(note)
return self._move_row(session, existing_plr, row_number) return self._move_row(session, existing_plr, row_number)
# Build playlist_row object # Build playlist_row object
@ -1355,7 +1360,8 @@ class PlaylistTab(QTableWidget):
return playlistrow_id return playlistrow_id
def _get_playlistrow_object(self, session: scoped_session, row: int) -> int: def _get_playlistrow_object(self, session: scoped_session,
row: int) -> int:
"""Return the playlistrow object associated with this row""" """Return the playlistrow object associated with this row"""
playlistrow_id = (self.item(row, USERDATA).data(self.PLAYLISTROW_ID)) playlistrow_id = (self.item(row, USERDATA).data(self.PLAYLISTROW_ID))
@ -1484,48 +1490,6 @@ class PlaylistTab(QTableWidget):
and pos.y() >= rect.center().y() # noqa W503 and pos.y() >= rect.center().y() # noqa W503
) )
def _meta_clear_attribute(self, row: int, attribute: int) -> None:
"""Clear given metadata for row"""
if row is None:
raise ValueError(f"_meta_clear_attribute({row=}, {attribute=})")
new_metadata: int = self._meta_get(row) & ~(1 << attribute)
self.item(row, USERDATA).setData(self.ROW_FLAGS, new_metadata)
def _meta_get(self, row: int) -> int:
"""Return row metadata"""
return (self.item(row, USERDATA).data(self.ROW_FLAGS))
def _meta_search(self, metadata: int, one: bool = True) -> List[int]:
"""
Search rows for metadata.
If one is True, check that only one row matches and return
the row number.
If one is False, return a list of matching row numbers.
"""
matches = []
for row in range(self.rowCount()):
if self._meta_get(row):
if self._meta_get(row) & (1 << metadata):
matches.append(row)
if not one:
return matches
if len(matches) <= 1:
return matches
else:
log.error(
f"Multiple matches for metadata '{metadata}' found "
f"in rows: {', '.join([str(x) for x in matches])}"
)
raise AttributeError(f"Multiple '{metadata}' metadata {matches}")
def _move_row(self, session: scoped_session, plr: PlaylistRows, def _move_row(self, session: scoped_session, plr: PlaylistRows,
new_row_number: int) -> None: new_row_number: int) -> None:
"""Move playlist row to new_row_number using parent copy/paste""" """Move playlist row to new_row_number using parent copy/paste"""

30
poetry.lock generated
View File

@ -306,7 +306,7 @@ python-versions = ">=3.7"
name = "mypy" name = "mypy"
version = "0.991" version = "0.991"
description = "Optional static typing for Python" description = "Optional static typing for Python"
category = "main" category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
@ -325,7 +325,7 @@ reports = ["lxml"]
name = "mypy-extensions" name = "mypy-extensions"
version = "0.4.3" version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker." description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "main" category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
@ -671,23 +671,11 @@ postgresql_psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql (<1)", "pymysql"] pymysql = ["pymysql (<1)", "pymysql"]
sqlcipher = ["sqlcipher3-binary"] sqlcipher = ["sqlcipher3-binary"]
[[package]]
name = "sqlalchemy-stubs"
version = "0.4"
description = "SQLAlchemy stubs and mypy plugin"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
mypy = ">=0.790"
typing-extensions = ">=3.7.4"
[[package]] [[package]]
name = "sqlalchemy2-stubs" name = "sqlalchemy2-stubs"
version = "0.0.2a31" version = "0.0.2a32"
description = "Typing Stubs for SQLAlchemy 1.4" description = "Typing Stubs for SQLAlchemy 1.4"
category = "main" category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
@ -760,7 +748,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
name = "tomli" name = "tomli"
version = "2.0.1" version = "2.0.1"
description = "A lil' TOML parser" description = "A lil' TOML parser"
category = "main" category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
@ -788,7 +776,7 @@ python-versions = "*"
name = "typing-extensions" name = "typing-extensions"
version = "4.4.0" version = "4.4.0"
description = "Backported and Experimental Type Hints for Python 3.7+" description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main" category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
@ -816,7 +804,7 @@ python-versions = "*"
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "9ae8194c057d5eb51e7f65f5328f94ea85e0a48a5fe12d38ad72f4a1aae7cbca" content-hash = "389d73715056202a39f0efe23941943d7a6849915607cb8ceb4c77bde7e4f709"
[metadata.files] [metadata.files]
alembic = [] alembic = []
@ -922,10 +910,6 @@ six = [
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
] ]
sqlalchemy = [] sqlalchemy = []
sqlalchemy-stubs = [
{file = "sqlalchemy-stubs-0.4.tar.gz", hash = "sha256:c665d6dd4482ef642f01027fa06c3d5e91befabb219dc71fc2a09e7d7695f7ae"},
{file = "sqlalchemy_stubs-0.4-py3-none-any.whl", hash = "sha256:5eec7aa110adf9b957b631799a72fef396b23ff99fe296df726645d01e312aa5"},
]
sqlalchemy2-stubs = [] sqlalchemy2-stubs = []
stack-data = [] stack-data = []
stackprinter = [] stackprinter = []

View File

@ -24,18 +24,17 @@ python-Levenshtein = "^0.12.2"
pyfzf = "^0.3.1" pyfzf = "^0.3.1"
pydymenu = "^0.5.2" pydymenu = "^0.5.2"
stackprinter = "^0.2.10" stackprinter = "^0.2.10"
sqlalchemy-stubs = "^0.4"
sqlalchemy2-stubs = "^0.0.2-alpha.31"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
ipdb = "^0.13.9" ipdb = "^0.13.9"
sqlalchemy-stubs = "^0.4"
PyQt5-stubs = "^5.15.2" PyQt5-stubs = "^5.15.2"
pytest = "^7.0.1" pytest = "^7.0.1"
pytest-qt = "^4.0.2" pytest-qt = "^4.0.2"
pydub-stubs = "^0.25.1" pydub-stubs = "^0.25.1"
line-profiler = "^4.0.2" line-profiler = "^4.0.2"
flakehell = "^0.9.0" flakehell = "^0.9.0"
sqlalchemy2-stubs = "^0.0.2-alpha.32"
mypy = "^0.991"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]
@ -45,3 +44,6 @@ build-backend = "poetry.core.masonry.api"
mypy_path = "/home/kae/.cache/pypoetry/virtualenvs/musicmuster-oWgGw1IG-py3.9:/home/kae/git/musicmuster/app" mypy_path = "/home/kae/.cache/pypoetry/virtualenvs/musicmuster-oWgGw1IG-py3.9:/home/kae/git/musicmuster/app"
plugins = "sqlalchemy.ext.mypy.plugin" plugins = "sqlalchemy.ext.mypy.plugin"
[tool.vulture]
exclude = ["migrations"]
paths = ["app"]