From f22f2780a301093e2c79b6a6e3e228d644c6d4c4 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Wed, 2 Mar 2022 20:37:27 +0000 Subject: [PATCH] Fix move tracks --- .idea/misc.xml | 2 +- .idea/musicmuster.iml | 2 +- app/models.py | 3 - app/musicmuster.py | 53 ++--- app/playlists.py | 438 ++++++++++++++++++++---------------------- poetry.lock | 27 ++- pyproject.toml | 7 +- 7 files changed, 257 insertions(+), 275 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 46766cb..58c660e 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/.idea/musicmuster.iml b/.idea/musicmuster.iml index 42a271d..a935670 100644 --- a/.idea/musicmuster.iml +++ b/.idea/musicmuster.iml @@ -4,7 +4,7 @@ - + diff --git a/app/models.py b/app/models.py index ca115bd..39a26c1 100644 --- a/app/models.py +++ b/app/models.py @@ -338,9 +338,6 @@ class Playlists(Base): self.loaded = True self.last_used = datetime.now() - if self not in session: - session.add(self) - session.commit() def remove_all_tracks(self, session: Session) -> None: """ diff --git a/app/musicmuster.py b/app/musicmuster.py index 0da712a..55a0a4d 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -88,7 +88,6 @@ class Window(QMainWindow, Ui_MainWindow): self.visible_playlist_tab().insert_track(session, track) def set_main_window_size(self) -> None: - # TODO: V2 check """Set size of window from database""" with Session() as session: @@ -355,7 +354,6 @@ class Window(QMainWindow, Ui_MainWindow): self.end_of_track_actions() def insert_note(self): - #TODO: V2 check "Add non-track row to playlist" dlg = QInputDialog(self) @@ -374,6 +372,7 @@ class Window(QMainWindow, Ui_MainWindow): with Session() as session: for playlist in Playlists.get_open(session): self.create_playlist_tab(session, playlist) + playlist.mark_open(session) def create_playlist_tab(self, session: Session, playlist: Playlists) -> None: @@ -383,7 +382,7 @@ class Window(QMainWindow, Ui_MainWindow): """ playlist_tab: PlaylistTab = PlaylistTab( - parent=self, session=session, playlist=playlist) + musicmuster=self, session=session, playlist_id=playlist.id) idx: int = self.tabPlaylist.addTab(playlist_tab, playlist.name) self.tabPlaylist.setCurrentIndex(idx) @@ -391,47 +390,37 @@ class Window(QMainWindow, Ui_MainWindow): """Move selected rows to another playlist""" with Session() as session: + visible_tab = self.visible_playlist_tab() + visible_tab_id = visible_tab.playlist_id + playlists = [p for p in Playlists.get_all(session) - if p.id != self.visible_playlist_tab().id] + if p.id != visible_tab_id] dlg = SelectPlaylistDialog(self, playlists=playlists) dlg.exec() if not dlg.plid: return - # TODO: just update dest playlist and call populate if - # visible + # Update database for both source and destination playlists + rows = visible_tab.get_selected_rows() + for row in rows: + PlaylistTracks.move_track( + session, visible_tab.playlist_id, row, dlg.plid + ) - # If destination playlist is visible, we need to add the moved - # tracks to it. If not, they will be automatically loaded when - # the playlistis opened. + # Update destination playlist_tab if visible (if not visible, it + # will be re-populated when it is opened) destination_visible_playlist_tab = None for tab in range(self.tabPlaylist.count()): - # Non-playlist tabs won't have a 'playlist' attribute - if not hasattr(self.tabPlaylist.widget(tab), 'playlist'): + # Non-playlist tabs won't have a 'playlist_id' attribute + if not hasattr(self.tabPlaylist.widget(tab), 'playlist_id'): continue - if self.tabPlaylist.widget(tab).id == dlg.plid: + if self.tabPlaylist.widget(tab).playlist_id == dlg.plid: destination_visible_playlist_tab = ( self.tabPlaylist.widget(tab)) break - rows = [] - for (row, track) in ( - self.visible_playlist_tab().get_selected_rows_and_tracks( - session) - ): - rows.append(row) - if destination_visible_playlist_tab: - # Insert with repaint=False to not update database - destination_visible_playlist_tab.insert_track( - session, track, repaint=False) - - # Update database for both source and destination playlists - PlaylistTracks.move_rows( - session, self.visible_playlist_tab().id, rows, dlg.plid) - - # Update destination playlist if visible - if destination_visible_playlist_tab: - destination_visible_playlist_tab.update_display() + if destination_visible_playlist_tab: + destination_visible_playlist_tab.populate(session, dlg.plid) # Update source playlist self.visible_playlist_tab().remove_rows(rows) @@ -577,8 +566,8 @@ class Window(QMainWindow, Ui_MainWindow): dlg = SelectPlaylistDialog(self, playlists=playlists) dlg.exec() if dlg.plid: - playlist = Playlists.get_by_id(session, dlg.plid) - self.create_playlist_tab(session, playlist) + p = Playlists.get_by_id(session=session, playlist_id=dlg.plid) + self.create_playlist_tab(session, p) def select_next_row(self) -> None: """Select next or first row in playlist""" diff --git a/app/playlists.py b/app/playlists.py index fb4c51c..0df0d49 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -65,13 +65,12 @@ class PlaylistTab(QTableWidget): ROW_METADATA = Qt.UserRole CONTENT_OBJECT = Qt.UserRole + 1 - def __init__(self, parent: QMainWindow, session: Session, - playlist: Playlists, *args, **kwargs): + def __init__(self, musicmuster: QMainWindow, session: Session, + playlist_id: int, *args, **kwargs): super().__init__(*args, **kwargs) - self.parent: QMainWindow = parent # The MusicMuster process - self.playlist: Playlists = playlist - self.playlist.mark_open(session) + self.musicmuster: QMainWindow = musicmuster + self.playlist_id: int = playlist_id self.menu: Optional[QMenu] = None self.current_track_start_time: Optional[datetime] = None @@ -140,13 +139,10 @@ class PlaylistTab(QTableWidget): self.cellEditingEnded.connect(self._cell_edit_ended) # Now load our tracks and notes - self._populate(session, playlist) + self.populate(session, self.playlist_id) def __repr__(self) -> str: - return ( - f"" - ) + return (f" Optional[int]: @@ -296,19 +292,11 @@ class PlaylistTab(QTableWidget): else: return self.selectionModel().selectedRows()[0].row() - def get_selected_rows_and_tracks(self, session: Session) \ - -> Optional[List[Tuple[int, Tracks]]]: - """Return a list of selected (row-number, track) tuples""" + def get_selected_rows(self) -> List[int]: + """Return a list of selected row numbers""" - if not self.selectionModel().hasSelection(): - return None - - result = [] - - for row in [r.row() for r in self.selectionModel().selectedRows()]: - result.append((row, self._get_row_object(row, session))) - - return result + rows = self.selectionModel().selectedRows() + return [row.row() for row in rows] def get_selected_title(self) -> Optional[str]: """Return title of selected row or None""" @@ -448,6 +436,49 @@ class PlaylistTab(QTableWidget): self._clear_current_track_row() self.current_track_start_time = None + def populate(self, session: Session, playlist_id: int) -> None: + """ + Populate from the associated playlist ID + + We don't mandate that an item will be on its specified row, only + that it will be above larger-numbered row items, and below + lower-numbered ones. + """ + + data: List[Union[Tuple[List[int], Tracks], Tuple[List[int], Notes]]] \ + = [] + item: Union[Notes, Tracks] + note: Notes + row: int + track: Tracks + + playlist = Playlists.get_by_id(session, playlist_id) + + for row, track in playlist.tracks.items(): + data.append(([row], track)) + for note in playlist.notes: + data.append(([note.row], note)) + + # Clear playlist + self.setRowCount(0) + + # Now add data in row order + for i in sorted(data, key=lambda x: x[0]): + item = i[1] + if isinstance(item, Tracks): + self.insert_track(session, item, repaint=False) + elif isinstance(item, Notes): + self._insert_note(session, item, repaint=False) + + # Scroll to top + scroll_to: QTableWidgetItem = self.item(0, self.COL_TITLE) + self.scrollToItem(scroll_to, QAbstractItemView.PositionAtTop) + + # We possibly don't need to save the playlist here, but row + # numbers may have changed during population, and it's cheap to do + self.save_playlist(session) + self.update_display(session) + def save_playlist(self, session) -> None: """ Save playlist to database. @@ -461,8 +492,7 @@ class PlaylistTab(QTableWidget): changes. """ - # Ensure we have a valid database class - session.add(self.playlist) + playlist = Playlists.get_by_id(session, self.playlist_id) # Notes first # Create dictionaries indexed by note_id @@ -477,7 +507,7 @@ class PlaylistTab(QTableWidget): playlist_notes[note.id] = note # Database - for note in self.playlist.notes: + for note in playlist.notes: database_notes[note.id] = note # We don't need to check for notes to add to the database as @@ -505,14 +535,14 @@ class PlaylistTab(QTableWidget): # Tracks # Remove all tracks from this playlist - self.playlist.remove_all_tracks(session) + playlist.remove_all_tracks(session) # Iterate on-screen playlist and add tracks back in for row in range(self.rowCount()): if row in notes_rows: continue track_id: int = self.item( row, self.COL_USERDATA).data(self.CONTENT_OBJECT) - self.playlist.add_track(session, track_id, row) + playlist.add_track(session, track_id, row) def select_next_row(self) -> None: """ @@ -616,17 +646,13 @@ class PlaylistTab(QTableWidget): - Show unplayed tracks in bold """ - if self.playlist not in session: - session.add(self.playlist) - DEBUG(f"playlist.update_display [{self.playlist=}]") - # Clear selection if required if clear_selection: self.clearSelection() current_row: Optional[int] = self._get_current_track_row() next_row: Optional[int] = self._get_next_track_row() - notes: Optional[List[int]] = self._get_notes_rows() + notes: List[int] = self._get_notes_rows() played: Optional[List[int]] = self._get_played_track_rows() unreadable: Optional[List[int]] = self._get_unreadable_track_rows() @@ -871,7 +897,7 @@ class PlaylistTab(QTableWidget): with Session() as session: self.update_display(session) - self.parent.enable_play_next_controls() + self.musicmuster.enable_play_next_controls() def _cell_edit_started(self, row: int, column: int) -> None: """ @@ -883,7 +909,28 @@ class PlaylistTab(QTableWidget): self.editing_cell = True # Disable play controls so that keyboard input doesn't disturb playing - self.parent.disable_play_next_controls() + self.musicmuster.disable_play_next_controls() + + def _clear_current_track_row(self) -> None: + """ + Clear current row if there is one. + """ + + current_row: Optional[int] = self._get_current_track_row() + if current_row is not None: + self._meta_clear_attribute(current_row, RowMeta.CURRENT) + # Reset row colour + if current_row % 2: + self._set_row_colour( + current_row, QColor(Config.COLOUR_ODD_PLAYLIST)) + else: + self._set_row_colour( + current_row, QColor(Config.COLOUR_EVEN_PLAYLIST)) + + def _clear_played_row_status(self, row: int) -> None: + """Clear played status on row""" + + self._meta_clear_attribute(row, RowMeta.PLAYED) def _delete_rows(self) -> None: """Delete mutliple rows""" @@ -932,6 +979,67 @@ class PlaylistTab(QTableWidget): return (index.row() + 1 if self._is_below(event.pos(), index) else index.row()) + def _edit_cell(self, mi): # review + """Called when table is double-clicked""" + + row = mi.row() + column = mi.column() + item = self.item(row, column) + + if column in [self.COL_TITLE, self.COL_ARTIST]: + self.editItem(item) + + @staticmethod + def _file_is_readable(path: str) -> bool: + """ + Returns True if track path is readable, else False + + vlc cannot read files with a colon in the path + """ + + if os.access(path, os.R_OK): + if ':' not in path: + return True + + return False + + def _find_next_track_row(self, starting_row: int = None) -> Optional[int]: + """ + Find next track to play. If a starting row is given, start there; + else if there's a track selected, start looking from next track; + otherwise, start from top. Skip rows already played. + + If not found, return None. + + If found, return row number. + """ + + if starting_row is None: + current_row = self._get_current_track_row() + if current_row is not None: + starting_row = current_row + 1 + else: + starting_row = 0 + notes_rows = self._get_notes_rows() + played_rows = self._get_played_track_rows() + for row in range(starting_row, self.rowCount()): + if row in notes_rows or row in played_rows: + continue + else: + return row + + return None + + def _get_current_track_row(self) -> Optional[int]: + """Return row marked as current, or None""" + + return self._meta_search(RowMeta.CURRENT) + + def _get_next_track_row(self) -> Optional[int]: + """Return row marked as next, or None""" + + return self._meta_search(RowMeta.NEXT) + @staticmethod def _get_note_text_time(text: str) -> Optional[datetime]: """Return time specified at the end of text""" @@ -945,6 +1053,11 @@ class PlaylistTab(QTableWidget): except ValueError: return None + def _get_notes_rows(self) -> List[int]: + """Return rows marked as notes, or None""" + + return self._meta_search(RowMeta.NOTE, one=False) + def _get_row_end_time(self, row) -> Optional[datetime]: """ Return row end time as string @@ -961,14 +1074,6 @@ class PlaylistTab(QTableWidget): except ValueError: return None - def _get_row_track_object(self, row: int, session: Session) \ - -> Optional[Tracks]: - """Return track associated with this row""" - - track_id = self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT) - track = Tracks.get_by_id(session, track_id) - return track - def _get_row_notes_object(self, row: int, session: Session) \ -> Optional[Notes]: """Return note associated with this row""" @@ -977,6 +1082,11 @@ class PlaylistTab(QTableWidget): note = Notes.get_by_id(session, note_id) return note + def _get_played_track_rows(self) -> Optional[List[int]]: + """Return rows marked as played, or None""" + + return self._meta_search(RowMeta.PLAYED, one=False) + def _get_row_start_time(self, row: int) -> Optional[datetime]: try: if self.item(row, self.COL_START_TIME): @@ -989,6 +1099,24 @@ class PlaylistTab(QTableWidget): except ValueError: return None + def _get_row_track_object(self, row: int, session: Session) \ + -> Optional[Tracks]: + """Return track associated with this row""" + + track_id = self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT) + track = Tracks.get_by_id(session, track_id) + return track + + def _get_track_rows(self) -> Optional[List[int]]: + """Return rows marked as tracks, or None""" + + return self._meta_notset(RowMeta.NOTE) + + def _get_unreadable_track_rows(self) -> Optional[List[int]]: + """Return rows marked as unreadable, or None""" + + return self._meta_search(RowMeta.UNREADABLE, one=False) + def _info_row(self, row: int) -> None: """Display popup with info re row""" @@ -1071,43 +1199,6 @@ class PlaylistTab(QTableWidget): and pos.y() >= rect.center().y() # noqa W503 ) - def _edit_cell(self, mi): # review - """Called when table is double-clicked""" - - row = mi.row() - column = mi.column() - item = self.item(row, column) - - if column in [self.COL_TITLE, self.COL_ARTIST]: - self.editItem(item) - - def _find_next_track_row(self, starting_row: int = None) -> Optional[int]: - """ - Find next track to play. If a starting row is given, start there; - else if there's a track selected, start looking from next track; - otherwise, start from top. Skip rows already played. - - If not found, return None. - - If found, return row number. - """ - - if starting_row is None: - current_row = self._get_current_track_row() - if current_row is not None: - starting_row = current_row + 1 - else: - starting_row = 0 - notes_rows = self._get_notes_rows() - played_rows = self._get_played_track_rows() - for row in range(starting_row, self.rowCount()): - if row in notes_rows or row in played_rows: - continue - else: - return row - - return None - def _meta_clear_attribute(self, row: int, attribute: int) -> None: """Clear given metadata for row""" @@ -1118,22 +1209,6 @@ class PlaylistTab(QTableWidget): self.item(row, self.COL_USERDATA).setData( self.ROW_METADATA, new_metadata) - def _clear_current_track_row(self) -> None: - """ - Clear current row if there is one. - """ - - current_row: Optional[int] = self._get_current_track_row() - if current_row is not None: - self._meta_clear_attribute(current_row, RowMeta.CURRENT) - # Reset row colour - if current_row % 2: - self._set_row_colour( - current_row, QColor(Config.COLOUR_ODD_PLAYLIST)) - else: - self._set_row_colour( - current_row, QColor(Config.COLOUR_EVEN_PLAYLIST)) - def _meta_clear_next(self) -> None: """ Clear next row if there is one. @@ -1143,46 +1218,11 @@ class PlaylistTab(QTableWidget): if next_row is not None: self._meta_clear_attribute(next_row, RowMeta.NEXT) - def _clear_played_row_status(self, row: int) -> None: - """Clear played status on row""" - - self._meta_clear_attribute(row, RowMeta.PLAYED) - def _meta_get(self, row: int) -> Optional[int]: """Return row metadata""" return self.item(row, self.COL_USERDATA).data(self.ROW_METADATA) - def _get_current_track_row(self) -> Optional[int]: - """Return row marked as current, or None""" - - return self._meta_search(RowMeta.CURRENT) - - def _get_next_track_row(self) -> Optional[int]: - """Return row marked as next, or None""" - - return self._meta_search(RowMeta.NEXT) - - def _get_notes_rows(self) -> Optional[List[int]]: - """Return rows marked as notes, or None""" - - return self._meta_search(RowMeta.NOTE, one=False) - - def _get_track_rows(self) -> Optional[List[int]]: - """Return rows marked as tracks, or None""" - - return self._meta_notset(RowMeta.NOTE) - - def _get_played_track_rows(self) -> Optional[List[int]]: - """Return rows marked as played, or None""" - - return self._meta_search(RowMeta.PLAYED, one=False) - - def _get_unreadable_track_rows(self) -> Optional[List[int]]: - """Return rows marked as unreadable, or None""" - - return self._meta_search(RowMeta.UNREADABLE, one=False) - def _meta_notset(self, metadata: int) -> Union[List[int]]: """ Search rows for metadata not set. @@ -1243,6 +1283,21 @@ class PlaylistTab(QTableWidget): self.item(row, self.COL_USERDATA).setData( self.ROW_METADATA, new_metadata) + def _rescan(self, row: int) -> None: + """ + If passed row is track row, rescan it. + Otherwise, return None. + """ + + DEBUG(f"_rescan({row=})") + + with Session() as session: + for row in self._get_track_rows(): + track: Tracks = self._get_row_track_object(row, session) + if track: + track.rescan(session) + self._update_row(session, row, track) + def _set_current_track_row(self, row: int) -> None: """Mark this row as current track""" @@ -1270,65 +1325,6 @@ class PlaylistTab(QTableWidget): self._meta_set_attribute(row, RowMeta.UNREADABLE) - def _populate(self, session: Session, playlist: Playlists) -> None: - """ - Populate from the associated playlist object - - We don't mandate that an item will be on its specified row, only - that it will be above larger-numbered row items, and below - lower-numbered ones. - """ - - data: List[Union[Tuple[List[int], Tracks], Tuple[List[int], Notes]]] \ - = [] - item: Union[Notes, Tracks] - note: Notes - row: int - track: Tracks - - if playlist not in session: - session.add(playlist) - - for row, track in self.playlist.tracks.items(): - data.append(([row], track)) - for note in self.playlist.notes: - data.append(([note.row], note)) - - # Clear playlist - self.setRowCount(0) - - # Now add data in row order - for i in sorted(data, key=lambda x: x[0]): - item = i[1] - if isinstance(item, Tracks): - self.insert_track(session, item, repaint=False) - elif isinstance(item, Notes): - self._insert_note(session, item, repaint=False) - - # Scroll to top - scroll_to: QTableWidgetItem = self.item(0, self.COL_TITLE) - self.scrollToItem(scroll_to, QAbstractItemView.PositionAtTop) - - # We possibly don't need to save the playlist here, but row - # numbers may have changed during population, and it's cheap to do - self.save_playlist(session) - self.update_display(session) - - def _rescan(self, row: int) -> None: - """ - If passed row is track row, rescan it. - Otherwise, return None. - """ - - DEBUG(f"_rescan({row=})") - - with Session() as session: - for row in self._get_track_rows(): - track: Tracks = self._get_row_track_object(row, session) - if track: - track.rescan(session) - self._update_row(session, row, track) - def _select_event(self) -> None: """ Called when item selection changes. @@ -1346,10 +1342,31 @@ class PlaylistTab(QTableWidget): # Only paint message if there are selected track rows if ms > 0: - self.parent.lblSumPlaytime.setText( + self.musicmuster.lblSumPlaytime.setText( f"Selected duration: {helpers.ms_to_mmss(ms)}") else: - self.parent.lblSumPlaytime.setText("") + self.musicmuster.lblSumPlaytime.setText("") + + def _select_tracks(self, played: bool) -> None: + """ + Select all played (played=True) or unplayed (played=False) + tracks in playlist + """ + + # Need to allow multiple rows to be selected + self.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) + notes_rows: List[int] = self._get_notes_rows() + self.clearSelection() + + played_rows: List[int] = self._get_played_track_rows() + for row in range(self.rowCount()): + if row in notes_rows: + continue + if row in played_rows == played: + self.selectRow(row) + + # Reset extended selection + self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) def _set_column_widths(self) -> None: """Column widths from settings""" @@ -1394,7 +1411,7 @@ class PlaylistTab(QTableWidget): self._set_next_track_row(row) # Notify musicmuster - self.parent.this_is_the_next_track(self, track) + self.musicmuster.this_is_the_next_track(self, track) # Update display self.update_display(session) @@ -1453,41 +1470,6 @@ class PlaylistTab(QTableWidget): item: QTableWidgetItem = QTableWidgetItem(time_str) self.setItem(row, self.COL_START_TIME, item) - def _select_tracks(self, played: bool) -> None: - """ - Select all played (played=True) or unplayed (played=False) - tracks in playlist - """ - - # Need to allow multiple rows to be selected - self.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) - notes_rows: List[int] = self._get_notes_rows() - self.clearSelection() - - played_rows: List[int] = self._get_played_track_rows() - for row in range(self.rowCount()): - if row in notes_rows: - continue - if row in played_rows == played: - self.selectRow(row) - - # Reset extended selection - self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - - @staticmethod - def _file_is_readable(path: str) -> bool: - """ - Returns True if track path is readable, else False - - vlc cannot read files with a colon in the path - """ - - if os.access(path, os.R_OK): - if ':' not in path: - return True - - return False - def _update_row(self, session, row: int, track: Tracks) -> None: """ Update the passed row with info from the passed track. diff --git a/poetry.lock b/poetry.lock index ba8899f..6734441 100644 --- a/poetry.lock +++ b/poetry.lock @@ -217,7 +217,7 @@ python-versions = ">=3.5, <4" name = "mypy" version = "0.931" description = "Optional static typing for Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -234,7 +234,7 @@ python2 = ["typed-ast (>=1.4.0,<2)"] name = "mypy-extensions" version = "0.4.3" description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "main" +category = "dev" optional = false python-versions = "*" @@ -404,6 +404,17 @@ category = "main" optional = false python-versions = ">=3.5" +[[package]] +name = "pyqt5-stubs" +version = "5.15.2.0" +description = "PEP561 stub files for the PyQt5 framework" +category = "dev" +optional = false +python-versions = ">= 3.5" + +[package.extras] +build = ["docker (==4.2.0)"] + [[package]] name = "pyqtwebengine" version = "5.15.5" @@ -513,7 +524,7 @@ sqlcipher = ["sqlcipher3-binary"] name = "sqlalchemy-stubs" version = "0.4" description = "SQLAlchemy stubs and mypy plugin" -category = "main" +category = "dev" optional = false python-versions = "*" @@ -560,7 +571,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" @@ -579,7 +590,7 @@ test = ["pytest"] name = "typing-extensions" version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -594,7 +605,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "6ea248abf2050b4d2fb08baaf78fee1b358e43109bb1bbf202a4d3e78a35ec53" +content-hash = "c9f7a8c9a15812c1f3affeaa301fe6e6310388cca41a229bdb97fdd58c765e6f" [metadata.files] alembic = [ @@ -910,6 +921,10 @@ pyqt5-sip = [ {file = "PyQt5_sip-12.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:989d51c41456cc496cb96f0b341464932b957040d26561f0bb4cf5a0914d6b36"}, {file = "PyQt5_sip-12.9.1.tar.gz", hash = "sha256:2f24f299b44c511c23796aafbbb581bfdebf78d0905657b7cee2141b4982030e"}, ] +pyqt5-stubs = [ + {file = "PyQt5-stubs-5.15.2.0.tar.gz", hash = "sha256:dc0dea66f02fe297fb0cddd5767fbf58275b54ecb67f69ebeb994e1553bfb9b2"}, + {file = "PyQt5_stubs-5.15.2.0-py3-none-any.whl", hash = "sha256:4b750d04ffca1bb188615d1a4e7d655a1d81d30df6ee3488f0adfe09b78e3d36"}, +] pyqtwebengine = [ {file = "PyQtWebEngine-5.15.5-cp36-abi3-macosx_10_13_x86_64.whl", hash = "sha256:5c77f71d88d871bc7400c68ef6433fadc5bd57b86d1a9c4d8094cea42f3607f1"}, {file = "PyQtWebEngine-5.15.5-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:782aeee6bc8699bc029fe5c169a045c2bc9533d781cf3f5e9fb424b85a204e68"}, diff --git a/pyproject.toml b/pyproject.toml index 7229830..aaae534 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,15 +17,14 @@ psutil = "^5.9.0" PyQtWebEngine = "^5.15.5" pydub = "^0.25.1" PyQt5-sip = "^12.9.1" -mypy = "^0.931" -sqlalchemy-stubs = "^0.4" [tool.poetry.dev-dependencies] -mypy = "^0.931" pytest = "^7.0.0" ipdb = "^0.13.9" -sqlalchemy-stubs = "^0.4" pytest-qt = "^4.0.2" +sqlalchemy-stubs = "^0.4" +PyQt5-stubs = "^5.15.2" +mypy = "^0.931" [build-system] requires = ["poetry-core>=1.0.0"]