diff --git a/app/dbmanager.py b/app/dbmanager.py index 317367d..9f8c2ca 100644 --- a/app/dbmanager.py +++ b/app/dbmanager.py @@ -15,7 +15,7 @@ class DatabaseManager: __instance = None - def __init__(self, database_url: str, **kwargs): + def __init__(self, database_url: str, **kwargs: dict) -> None: if DatabaseManager.__instance is None: self.db = Alchemical(database_url, **kwargs) self.db.create_all() @@ -24,7 +24,7 @@ class DatabaseManager: raise Exception("Attempted to create a second DatabaseManager instance") @staticmethod - def get_instance(database_url: str, **kwargs): + def get_instance(database_url: str, **kwargs: dict) -> Alchemical: if DatabaseManager.__instance is None: DatabaseManager(database_url, **kwargs) return DatabaseManager.__instance diff --git a/app/dialogs.py b/app/dialogs.py index 85c3945..57a21b0 100644 --- a/app/dialogs.py +++ b/app/dialogs.py @@ -10,6 +10,7 @@ from PyQt6.QtWidgets import ( QListWidgetItem, QMainWindow, QTableWidgetItem, + QWidget, ) # Third party imports @@ -39,10 +40,10 @@ class ReplaceFilesDialog(QDialog): self, session: Session, main_window: QMainWindow, - *args, - **kwargs, + *args: Qt.WindowType, + **kwargs: Qt.WindowType, ) -> None: - super().__init__(*args, **kwargs) + super().__init__(main_window, *args, **kwargs) self.session = session self.main_window = main_window self.ui = dlg_replace_files_ui.Ui_Dialog() @@ -232,18 +233,19 @@ class TrackSelectDialog(QDialog): def __init__( self, + parent: QMainWindow, session: Session, new_row_number: int, source_model: PlaylistModel, add_to_header: Optional[bool] = False, - *args, - **kwargs, + *args: Qt.WindowType, + **kwargs: Qt.WindowType, ) -> None: """ Subclassed QDialog to manage track selection """ - super().__init__(*args, **kwargs) + super().__init__(parent, *args, **kwargs) self.session = session self.new_row_number = new_row_number self.source_model = source_model diff --git a/app/infotabs.py b/app/infotabs.py index 9cfbccb..2901620 100644 --- a/app/infotabs.py +++ b/app/infotabs.py @@ -2,12 +2,12 @@ import urllib.parse import datetime as dt from slugify import slugify # type: ignore -from typing import Dict +from typing import Dict, Optional # PyQt imports -from PyQt6.QtCore import QUrl # type: ignore +from PyQt6.QtCore import QUrl from PyQt6.QtWebEngineWidgets import QWebEngineView -from PyQt6.QtWidgets import QTabWidget +from PyQt6.QtWidgets import QTabWidget, QWidget # Third party imports @@ -22,7 +22,7 @@ class InfoTabs(QTabWidget): Class to manage info tabs """ - def __init__(self, parent=None) -> None: + def __init__(self, parent: Optional[QWidget] = None) -> None: super().__init__(parent) self.signals = MusicMusterSignals() diff --git a/app/log.py b/app/log.py index ae44792..77f3b9c 100644 --- a/app/log.py +++ b/app/log.py @@ -19,7 +19,7 @@ from config import Config class LevelTagFilter(logging.Filter): """Add leveltag""" - def filter(self, record: logging.LogRecord): + def filter(self, record: logging.LogRecord) -> bool: # Extract the first character of the level name record.leveltag = record.levelname[0] diff --git a/app/models.py b/app/models.py index d6da42a..a348e0c 100644 --- a/app/models.py +++ b/app/models.py @@ -110,7 +110,7 @@ class Playdates(dbtables.PlaydatesTable): @staticmethod def last_playdates( - session: Session, track_id: int, limit=5 + session: Session, track_id: int, limit: int = 5 ) -> Sequence["Playdates"]: """ Return a list of the last limit playdates for this track, sorted @@ -143,7 +143,7 @@ class Playdates(dbtables.PlaydatesTable): return Config.EPOCH # pragma: no cover @staticmethod - def last_played_tracks(session: Session, limit=5) -> Sequence["Playdates"]: + def last_played_tracks(session: Session, limit: int = 5) -> Sequence["Playdates"]: """ Return a list of the last limit tracks played, sorted earliest to latest. @@ -628,7 +628,7 @@ class Tracks(dbtables.TracksTable): raise ValueError(error) @classmethod - def get_all(cls, session) -> List["Tracks"]: + def get_all(cls, session: Session) -> Sequence["Tracks"]: """Return a list of all tracks""" return session.scalars(select(cls)).unique().all() diff --git a/app/musicmuster.py b/app/musicmuster.py index a5c67b3..6e863be 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -37,6 +37,7 @@ from PyQt6.QtWidgets import ( QListWidgetItem, QMainWindow, QMessageBox, + QWidget, ) # Third party imports @@ -257,7 +258,7 @@ class PreviewManager: class Window(QMainWindow, Ui_MainWindow): - def __init__(self, parent=None, *args, **kwargs) -> None: + def __init__(self, parent: Optional[QWidget] = None, *args: list, **kwargs: dict) -> None: super().__init__(parent) self.setupUi(self) @@ -866,6 +867,7 @@ class Window(QMainWindow, Ui_MainWindow): ) with db.Session() as session: dlg = TrackSelectDialog( + parent=self, session=session, new_row_number=new_row_number, source_model=self.active_proxy_model(), diff --git a/app/pipeclient.py b/app/pipeclient.py index 9c6f76c..858b3c5 100755 --- a/app/pipeclient.py +++ b/app/pipeclient.py @@ -70,6 +70,7 @@ Released under terms of the GNU General Public License version 2: """ +from io import TextIOWrapper import os import sys import threading @@ -77,6 +78,7 @@ import time import errno import argparse +from typing import Optional if sys.version_info[0] < 3: raise RuntimeError("PipeClient Error: Python 3.x required") @@ -135,11 +137,11 @@ class PipeClient: self.__dict__ = cls._shared_state return self - def __init__(self): - self.timer: bool = False # type: ignore - self._start_time: float = 0 # type: ignore - self._write_pipe = None - self.reply: str = "" # type: ignore + def __init__(self) -> None: + self.timer: bool = False + self._start_time: float = 0 + self._write_pipe: Optional[TextIOWrapper] = None + self.reply: str = "" if not self._write_pipe: self._write_thread_start() self._read_thread_start() @@ -166,7 +168,7 @@ class PipeClient: read_thread.daemon = True read_thread.start() - def write(self, command, timer=False) -> None: + def write(self, command: str, timer: Optional[bool] = False) -> None: """Write a command to _write_pipe. Parameters @@ -181,7 +183,13 @@ class PipeClient: write("GetInfo: Type=Labels", timer=True): """ - self.timer = timer + + # If write pipe not defined, return + if self._write_pipe is None: + return + + if timer: + self.timer = timer self._write_pipe.write(command + EOL) # Check that read pipe is alive if PipeClient.reader_pipe_broken.is_set(): @@ -240,7 +248,7 @@ class PipeClient: return self.reply -def bool_from_string(strval) -> bool: +def bool_from_string(strval: str) -> bool: """Return boolean value from string""" if strval.lower() in ("true", "t", "1", "yes", "y"): return True diff --git a/app/playlistmodel.py b/app/playlistmodel.py index 1f785f9..456197d 100644 --- a/app/playlistmodel.py +++ b/app/playlistmodel.py @@ -12,6 +12,7 @@ import re from PyQt6.QtCore import ( QAbstractTableModel, QModelIndex, + QObject, QRegularExpression, QSortFilterProxyModel, Qt, @@ -113,9 +114,9 @@ class PlaylistModel(QAbstractTableModel): def __init__( self, playlist_id: int, - *args, - **kwargs, - ): + *args: Optional[QObject], + **kwargs: Optional[QObject], + ) -> None: log.debug(f"PlaylistModel.__init__({playlist_id=})") self.playlist_id = playlist_id @@ -309,7 +310,7 @@ class PlaylistModel(QAbstractTableModel): session.commit() - def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole): + def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> QVariant: """Return data to view""" if not index.isValid() or not (0 <= index.row() < len(self.playlist_rows)): @@ -330,7 +331,7 @@ class PlaylistModel(QAbstractTableModel): } if role in dispatch_table: - return dispatch_table[role](row, column, prd) + return QVariant(dispatch_table[role](row, column, prd)) # Document other roles but don't use them if role in [ @@ -1081,7 +1082,7 @@ class PlaylistModel(QAbstractTableModel): # Update display self.invalidate_row(track_sequence.previous.row_number) - def refresh_data(self, session: db.session): + def refresh_data(self, session: db.session) -> None: """Populate dicts for data calls""" # Populate self.playlist_rows with playlist data @@ -1500,9 +1501,9 @@ class PlaylistProxyModel(QSortFilterProxyModel): def __init__( self, source_model: PlaylistModel, - *args, - **kwargs, - ): + *args: QObject, + **kwargs: QObject, + ) -> None: self.source_model = source_model super().__init__(*args, **kwargs) diff --git a/app/playlists.py b/app/playlists.py index b7becb5..c33ac3d 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -60,7 +60,7 @@ class EscapeDelegate(QStyledItemDelegate): - checks with user before abandoning edit on Escape """ - def __init__(self, parent, source_model: PlaylistModel) -> None: + def __init__(self, parent: QWidget, source_model: PlaylistModel) -> None: super().__init__(parent) self.source_model = source_model self.signals = MusicMusterSignals() @@ -70,7 +70,7 @@ class EscapeDelegate(QStyledItemDelegate): parent: Optional[QWidget], option: QStyleOptionViewItem, index: QModelIndex, - ): + ) -> Optional[QWidget]: """ Intercept createEditor call and make row just a little bit taller """ @@ -378,6 +378,7 @@ class PlaylistTab(QTableView): with db.Session() as session: dlg = TrackSelectDialog( + parent=self.musicmuster, session=session, new_row_number=model_row_number, source_model=self.source_model, diff --git a/app/trackmanager.py b/app/trackmanager.py index 624ddbf..0607f56 100644 --- a/app/trackmanager.py +++ b/app/trackmanager.py @@ -113,7 +113,7 @@ class _FadeCurve: if self.curve: self.curve.setPen(Config.FADE_CURVE_FOREGROUND) - def tick(self, play_time) -> None: + def tick(self, play_time: int) -> None: """Update volume fade curve""" if not self.GraphWidget: @@ -136,10 +136,10 @@ class _FadeCurve: class _FadeTrack(QRunnable): - def __init__(self, player: vlc.MediaPlayer, fade_seconds) -> None: + def __init__(self, player: vlc.MediaPlayer, fade_seconds: int) -> None: super().__init__() - self.player: vlc.MediaPlayer = player - self.fade_seconds: int = fade_seconds + self.player = player + self.fade_seconds = fade_seconds def run(self) -> None: """ @@ -172,11 +172,11 @@ class _Music: Manage the playing of music tracks """ - def __init__(self, name) -> None: + def __init__(self, name: str) -> None: self.VLC = vlc.Instance() self.VLC.set_user_agent(name, name) self.player: Optional[vlc.MediaPlayer] = None - self.name: str = name + self.name = name self.max_volume: int = Config.VLC_VOLUME_DEFAULT self.start_dt: Optional[dt.datetime] = None diff --git a/app/utilities.py b/app/utilities.py index 8c14f95..c367666 100755 --- a/app/utilities.py +++ b/app/utilities.py @@ -16,7 +16,7 @@ from log import log from models import Tracks -def check_db(session: Session): +def check_db(session: Session) -> None: """ Database consistency check. @@ -84,7 +84,7 @@ def check_db(session: Session): print("There were more paths than listed that were not found") -def update_bitrates(session: Session): +def update_bitrates(session: Session) -> None: """ Update bitrates on all tracks in database """ diff --git a/pyproject.toml b/pyproject.toml index eefe72d..dcaee51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,9 @@ build-backend = "poetry.core.masonry.api" [tool.mypy] mypy_path = "/home/kae/git/musicmuster/app" explicit_package_bases = true +python_version = 3.11 +warn_unused_configs = true +disallow_incomplete_defs = true [tool.pytest.ini_options] addopts = "--exitfirst --showlocals --capture=no"