Compare commits

..

No commits in common. "e3d20c9bdc64cf306d355a677199f9ea037b6061" and "262ab202fcc5394de0cf6760e42ff2ec001fe97c" have entirely different histories.

9 changed files with 129 additions and 224 deletions

View File

@ -83,11 +83,7 @@ class MusicMusterSignals(QObject):
add_track_to_playlist_signal = pyqtSignal(int, int, int, str) add_track_to_playlist_signal = pyqtSignal(int, int, int, str)
enable_escape_signal = pyqtSignal(bool) enable_escape_signal = pyqtSignal(bool)
next_track_changed_signal = pyqtSignal() next_track_changed_signal = pyqtSignal()
search_songfacts_signal = pyqtSignal(str)
search_wikipedia_signal = pyqtSignal(str)
show_warning_signal = pyqtSignal(str, str)
span_cells_signal = pyqtSignal(int, int, int, int) span_cells_signal = pyqtSignal(int, int, int, int)
status_message_signal = pyqtSignal(str, int)
def __post_init__(self): def __post_init__(self):
super().__init__() super().__init__()

1
app/icons_rc.py Symbolic link
View File

@ -0,0 +1 @@
ui/icons_rc.py

View File

@ -8,8 +8,6 @@ from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import QTabWidget from PyQt6.QtWidgets import QTabWidget
from config import Config from config import Config
from classes import MusicMusterSignals
class InfoTabs(QTabWidget): class InfoTabs(QTabWidget):
""" """
@ -19,9 +17,7 @@ class InfoTabs(QTabWidget):
def __init__(self, parent=None) -> None: def __init__(self, parent=None) -> None:
super().__init__(parent) super().__init__(parent)
self.signals = MusicMusterSignals() # Dictionary to record when tabs were last updated (so we can
self.signals.search_songfacts_signal.connect(self.open_in_songfacts)
self.signals.search_wikipedia_signal.connect(self.open_in_wikipedia)
# re-use the oldest one later) # re-use the oldest one later)
self.last_update: Dict[QWebEngineView, datetime] = {} self.last_update: Dict[QWebEngineView, datetime] = {}
self.tabtitles: Dict[int, str] = {} self.tabtitles: Dict[int, str] = {}

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from datetime import datetime, timedelta from datetime import datetime, timedelta
from os.path import basename
from time import sleep from time import sleep
from typing import ( from typing import (
cast, cast,
@ -73,7 +74,7 @@ from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
from ui.main_window_ui import Ui_MainWindow # type: ignore from ui.main_window_ui import Ui_MainWindow # type: ignore
from utilities import check_db, update_bitrates from utilities import check_db, update_bitrates
import helpers import helpers
from ui import icons_rc # noqa F401 import icons_rc # noqa F401
import music import music
@ -144,6 +145,46 @@ class CartButton(QPushButton):
self.pgb.setGeometry(0, 0, self.width(), 10) self.pgb.setGeometry(0, 0, self.width(), 10)
class ImportTrack(QObject):
import_error = pyqtSignal(str)
importing = pyqtSignal(str)
finished = pyqtSignal(PlaylistTab)
def __init__(self, playlist: PlaylistTab, filenames: list, row: int) -> None:
super().__init__()
self.filenames = filenames
self.playlist = playlist
self.row = row
def run(self):
"""
Create track objects from passed files and add to visible playlist
"""
target_row = self.row
with Session() as session:
for fname in self.filenames:
self.importing.emit(f"Importing {basename(fname)}")
metadata = helpers.get_file_metadata(fname)
try:
track = Tracks(session, **metadata)
except Exception as e:
print(e)
return
helpers.normalise_track(track.path)
self.playlist.insert_track(session, track, target_row)
# Insert next row under this one
target_row += 1
# We're importing potentially multiple tracks in a loop.
# If there's an error adding the track to the Tracks
# table, the session will rollback, thus losing any
# previous additions in this loop. So, commit now to
# lock in what we've just done.
session.commit()
self.playlist.save_playlist(session)
self.finished.emit(self.playlist)
class Window(QMainWindow, Ui_MainWindow): class Window(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None, *args, **kwargs) -> None: def __init__(self, parent=None, *args, **kwargs) -> None:
super().__init__(parent) super().__init__(parent)
@ -435,7 +476,7 @@ class Window(QMainWindow, Ui_MainWindow):
closing_tab_playlist_id = self.tabPlaylist.widget(tab_index).playlist_id closing_tab_playlist_id = self.tabPlaylist.widget(tab_index).playlist_id
if current_track_playlist_id: if current_track_playlist_id:
if closing_tab_playlist_id == current_track_playlist_id: if closing_tab_playlist_id == current_track_playlist_id:
self.show_status_message("Can't close current track playlist", 5000) self.statusbar.showMessage("Can't close current track playlist", 5000)
return False return False
# Record playlist as closed and update remaining playlist tabs # Record playlist as closed and update remaining playlist tabs
@ -482,10 +523,10 @@ class Window(QMainWindow, Ui_MainWindow):
self.actionResume.triggered.connect(self.resume) self.actionResume.triggered.connect(self.resume)
self.actionSave_as_template.triggered.connect(self.save_as_template) self.actionSave_as_template.triggered.connect(self.save_as_template)
self.actionSearch_title_in_Songfacts.triggered.connect( self.actionSearch_title_in_Songfacts.triggered.connect(
self.lookup_row_in_songfacts lambda: self.tabPlaylist.currentWidget().lookup_row_in_songfacts()
) )
self.actionSearch_title_in_Wikipedia.triggered.connect( self.actionSearch_title_in_Wikipedia.triggered.connect(
self.lookup_row_in_wikipedia lambda: self.tabPlaylist.currentWidget().lookup_row_in_wikipedia()
) )
self.actionSearch.triggered.connect(self.search_playlist) self.actionSearch.triggered.connect(self.search_playlist)
self.actionSelect_duplicate_rows.triggered.connect( self.actionSelect_duplicate_rows.triggered.connect(
@ -510,8 +551,6 @@ class Window(QMainWindow, Ui_MainWindow):
self.signals.enable_escape_signal.connect(self.enable_escape) self.signals.enable_escape_signal.connect(self.enable_escape)
self.signals.next_track_changed_signal.connect(self.update_headers) self.signals.next_track_changed_signal.connect(self.update_headers)
self.signals.status_message_signal.connect(self.show_status_message)
self.signals.show_warning_signal.connect(self.show_warning)
self.timer10.timeout.connect(self.tick_10ms) self.timer10.timeout.connect(self.tick_10ms)
self.timer500.timeout.connect(self.tick_500ms) self.timer500.timeout.connect(self.tick_500ms)
@ -599,7 +638,7 @@ class Window(QMainWindow, Ui_MainWindow):
""" """
self.actionPlay_next.setEnabled(False) self.actionPlay_next.setEnabled(False)
self.show_status_message("Play controls: Disabled", 0) self.statusbar.showMessage("Play controls: Disabled", 0)
def download_played_tracks(self) -> None: def download_played_tracks(self) -> None:
"""Download a CSV of played tracks""" """Download a CSV of played tracks"""
@ -650,7 +689,7 @@ class Window(QMainWindow, Ui_MainWindow):
""" """
self.actionPlay_next.setEnabled(True) self.actionPlay_next.setEnabled(True)
self.show_status_message("Play controls: Enabled", 0) self.statusbar.showMessage("Play controls: Enabled", 0)
def export_playlist_tab(self) -> None: def export_playlist_tab(self) -> None:
"""Export the current playlist to an m3u file""" """Export the current playlist to an m3u file"""
@ -747,21 +786,7 @@ class Window(QMainWindow, Ui_MainWindow):
txt = "" txt = ""
tags = helpers.get_tags(fname) tags = helpers.get_tags(fname)
title = tags["title"] title = tags["title"]
if not title:
helpers.show_warning(
self,
"Problem with track file",
f"{fname} does not have a title tag",
)
continue
artist = tags["artist"] artist = tags["artist"]
if not artist:
helpers.show_warning(
self,
"Problem with track file",
f"{fname} does not have an artist tag",
)
continue
count = 0 count = 0
possible_matches = Tracks.search_titles(session, title) possible_matches = Tracks.search_titles(session, title)
if possible_matches: if possible_matches:
@ -788,10 +813,33 @@ class Window(QMainWindow, Ui_MainWindow):
continue continue
new_tracks.append(fname) new_tracks.append(fname)
# Pass to model to manage the import # Import in separate thread
self.active_model().import_tracks( self.import_thread = QThread()
new_tracks, self.active_tab().get_selected_row_number() self.worker = ImportTrack(
self.active_tab(),
new_tracks,
self.active_tab().get_new_row_number(),
) )
self.worker.moveToThread(self.import_thread)
self.import_thread.started.connect(self.worker.run)
self.worker.finished.connect(self.import_thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.import_thread.finished.connect(self.import_thread.deleteLater)
self.worker.import_error.connect(
lambda msg: helpers.show_warning(
self, "Import error", "Error importing " + msg
)
)
self.worker.importing.connect(lambda msg: self.statusbar.showMessage(msg, 5000))
self.worker.finished.connect(self.import_complete)
self.import_thread.start()
def import_complete(self):
"""
Called by thread when track import complete
"""
self.statusbar.showMessage("Imports complete")
def insert_header(self) -> None: def insert_header(self) -> None:
"""Show dialog box to enter header text and add to playlist""" """Show dialog box to enter header text and add to playlist"""
@ -839,36 +887,6 @@ class Window(QMainWindow, Ui_MainWindow):
if record.f_int and record.f_int >= 0: if record.f_int and record.f_int >= 0:
self.tabPlaylist.setCurrentIndex(record.f_int) self.tabPlaylist.setCurrentIndex(record.f_int)
def lookup_row_in_songfacts(self) -> None:
"""
Display songfacts page for title in highlighted row
"""
row_number = self.active_tab().get_selected_row_number()
if row_number is None:
return
track_info = self.active_model().get_row_info(row_number)
if track_info is None:
return
self.signals.search_songfacts_signal.emit(track_info.title)
def lookup_row_in_wikipedia(self) -> None:
"""
Display Wikipedia page for title in highlighted row
"""
row_number = self.active_tab().get_selected_row_number()
if row_number is None:
return
track_info = self.active_model().get_row_info(row_number)
if track_info is None:
return
self.signals.search_wikipedia_signal.emit(track_info.title)
def move_playlist_rows( def move_playlist_rows(
self, session: scoped_session, playlistrows: Sequence[PlaylistRows] self, session: scoped_session, playlistrows: Sequence[PlaylistRows]
) -> None: ) -> None:
@ -1188,7 +1206,7 @@ class Window(QMainWindow, Ui_MainWindow):
playlist_id = self.active_tab().playlist_id playlist_id = self.active_tab().playlist_id
playlist = session.get(Playlists, playlist_id) playlist = session.get(Playlists, playlist_id)
if playlist: if playlist:
new_name = self.solicit_playlist_name(session, playlist.name) new_name = self.solicit_playlist_name(playlist.name)
if new_name: if new_name:
playlist.rename(session, new_name) playlist.rename(session, new_name)
idx = self.tabBar.currentIndex() idx = self.tabBar.currentIndex()
@ -1337,14 +1355,6 @@ class Window(QMainWindow, Ui_MainWindow):
# self.tabPlaylist.setCurrentWidget(self.current_track.playlist_tab) # self.tabPlaylist.setCurrentWidget(self.current_track.playlist_tab)
# self.tabPlaylist.currentWidget().scroll_current_to_top() # self.tabPlaylist.currentWidget().scroll_current_to_top()
def show_warning(self, title: str, body: str) -> None:
"""
Display a warning dialog
"""
print(f"show_warning({title=}, {body=})")
QMessageBox.warning(self, title, body)
def show_next(self) -> None: def show_next(self) -> None:
"""Scroll to show next track""" """Scroll to show next track"""
@ -1354,13 +1364,6 @@ class Window(QMainWindow, Ui_MainWindow):
# self.tabPlaylist.setCurrentWidget(self.next_track.playlist_tab) # self.tabPlaylist.setCurrentWidget(self.next_track.playlist_tab)
# self.tabPlaylist.currentWidget().scroll_next_to_top() # self.tabPlaylist.currentWidget().scroll_next_to_top()
def show_status_message(self, message: str, timing: int) -> None:
"""
Show status message in status bar for timing milliseconds
"""
self.statusbar.showMessage(message, timing)
def solicit_playlist_name( def solicit_playlist_name(
self, session: scoped_session, default: str = "" self, session: scoped_session, default: str = ""
) -> Optional[str]: ) -> Optional[str]:

View File

@ -2,16 +2,12 @@ from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
from enum import auto, Enum from enum import auto, Enum
from operator import attrgetter from operator import attrgetter
from os.path import basename
from typing import List, Optional from typing import List, Optional
from PyQt6.QtCore import ( from PyQt6.QtCore import (
pyqtSignal,
QAbstractTableModel, QAbstractTableModel,
QModelIndex, QModelIndex,
QObject,
Qt, Qt,
QThread,
QVariant, QVariant,
) )
from PyQt6.QtGui import ( from PyQt6.QtGui import (
@ -26,10 +22,8 @@ from dbconfig import scoped_session, Session
from helpers import ( from helpers import (
file_is_unreadable, file_is_unreadable,
get_embedded_time, get_embedded_time,
get_file_metadata,
get_relative_date, get_relative_date,
open_in_audacity, open_in_audacity,
normalise_track,
ms_to_mmss, ms_to_mmss,
set_track_metadata, set_track_metadata,
) )
@ -156,9 +150,7 @@ class PlaylistModel(QAbstractTableModel):
if playlist_id != self.playlist_id: if playlist_id != self.playlist_id:
return return
self.insert_row( self.insert_row(proposed_row_number=new_row_number, track_id=track_id, note=note)
proposed_row_number=new_row_number, track_id=track_id, note=note
)
def add_track_to_header( def add_track_to_header(
self, self,
@ -642,28 +634,6 @@ class PlaylistModel(QAbstractTableModel):
return prd.note return prd.note
def import_tracks(
self, new_tracks: List[str], proposed_row_number: Optional[int]
) -> None:
"""
Import the file paths listed in new_tracks. The files have already been sanitised
so no further checks needed. Import in a separate thread as this is slow.
"""
# Import in separate thread
self.import_thread = QThread()
self.worker = ImportTrack(
self,
new_tracks,
proposed_row_number,
)
self.worker.moveToThread(self.import_thread)
self.import_thread.started.connect(self.worker.run)
self.worker.import_finished.connect(self.import_thread.quit)
self.worker.import_finished.connect(self.worker.deleteLater)
self.import_thread.finished.connect(self.import_thread.deleteLater)
self.import_thread.start()
def is_header_row(self, row_number: int) -> bool: def is_header_row(self, row_number: int) -> bool:
""" """
Return True if row is a header row, else False Return True if row is a header row, else False
@ -854,16 +824,11 @@ class PlaylistModel(QAbstractTableModel):
def remove_track(self, row_number: int) -> None: def remove_track(self, row_number: int) -> None:
""" """
Remove track from row, retaining row as a header row Remove track from row
""" """
with Session() as session: # TODO
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid) print(f"remove_track({row_number=})")
if plr:
plr.track_id = None
self.refresh_row(session, row_number)
self.invalidate_row(row_number)
def rescan_track(self, row_number: int) -> None: def rescan_track(self, row_number: int) -> None:
""" """
@ -917,7 +882,6 @@ class PlaylistModel(QAbstractTableModel):
return return
track_sequence.next = PlaylistTrack() track_sequence.next = PlaylistTrack()
self.invalidate_row(next_row_was) self.invalidate_row(next_row_was)
self.signals.next_track_changed_signal.emit()
return return
# Update playing_trtack # Update playing_trtack
@ -941,7 +905,6 @@ class PlaylistModel(QAbstractTableModel):
return return
track_sequence.next.set_plr(session, plr) track_sequence.next.set_plr(session, plr)
self.signals.next_track_changed_signal.emit() self.signals.next_track_changed_signal.emit()
self.signals.search_wikipedia_signal.emit(self.playlist_rows[row_number].title)
self.invalidate_row(row_number) self.invalidate_row(row_number)
self.update_track_times() self.update_track_times()
@ -1116,46 +1079,3 @@ class PlaylistModel(QAbstractTableModel):
self.index(updated_row, Col.START_TIME.value), self.index(updated_row, Col.START_TIME.value),
self.index(updated_row, Col.END_TIME.value), self.index(updated_row, Col.END_TIME.value),
) )
class ImportTrack(QObject):
import_finished = pyqtSignal()
def __init__(self, model: PlaylistModel, filenames: List[str], row_number) -> None:
super().__init__()
self.model = model
self.filenames = filenames
self.row_number = row_number
self.signals = MusicMusterSignals()
def run(self):
"""
Create track objects from passed files and add to visible playlist
"""
target_row = self.row_number
with Session() as session:
for fname in self.filenames:
self.signals.status_message_signal.emit(
f"Importing {basename(fname)}", 5000
)
metadata = get_file_metadata(fname)
try:
track = Tracks(session, **metadata)
except Exception as e:
self.signals.show_warning_signal.emit("Error importing track", e)
return
normalise_track(track.path)
self.model.insert_row(self.row_number, track.id)
# Insert next row under this one
target_row += 1
# We're importing potentially multiple tracks in a loop.
# If there's an error adding the track to the Tracks
# table, the session will rollback, thus losing any
# previous additions in this loop. So, commit now to
# lock in what we've just done.
session.commit()
self.signals.status_message_signal.emit(
f"{len(self.filenames)} tracks imported", 10000
)
self.import_finished.emit()

View File

@ -472,7 +472,7 @@ class PlaylistTab(QTableView):
Set selected row as next track Set selected row as next track
""" """
selected_row = self.get_selected_row_number() selected_row = self._get_selected_row()
if selected_row is None: if selected_row is None:
return return
model = cast(PlaylistModel, self.model()) model = cast(PlaylistModel, self.model())
@ -541,13 +541,13 @@ class PlaylistTab(QTableView):
# Mark unplayed # Mark unplayed
if track_row and model.is_unplayed_row(row_number): if track_row and model.is_unplayed_row(row_number):
self._add_context_menu( self._add_context_menu(
"Mark unplayed", lambda: self._mark_as_unplayed(self._get_selected_rows()) "Mark unplayed", lambda: model.mark_unplayed(self._get_selected_rows())
) )
# Unmark as next # Unmark as next
if next_row: if next_row:
self._add_context_menu( self._add_context_menu(
"Unmark as next track", lambda: self._unmark_as_next() "Unmark as next track", lambda: model.set_next_row(None)
) )
# ---------------------- # ----------------------
@ -582,7 +582,7 @@ class PlaylistTab(QTableView):
# Track path TODO # Track path TODO
if track_row: if track_row:
self._add_context_menu("Copy track path", lambda: self._copy_path(row_number)) self._add_context_menu("Copy track path", lambda: print("Track path"))
def _calculate_end_time( def _calculate_end_time(
self, start: Optional[datetime], duration: int self, start: Optional[datetime], duration: int
@ -624,8 +624,7 @@ class PlaylistTab(QTableView):
to the clipboard. Otherwise, return None. to the clipboard. Otherwise, return None.
""" """
model = cast(PlaylistModel, self.model()) track_path = self._get_row_track_path(row_number)
track_path = model.get_row_info(row_number).path
if not track_path: if not track_path:
return return
@ -639,9 +638,8 @@ class PlaylistTab(QTableView):
track_path = track_path.replace(old, new) track_path = track_path.replace(old, new)
cb = QApplication.clipboard() cb = QApplication.clipboard()
if cb: cb.clear(mode=cb.Mode.Clipboard)
cb.clear(mode=cb.Mode.Clipboard) cb.setText(track_path, mode=cb.Mode.Clipboard)
cb.setText(track_path, mode=cb.Mode.Clipboard)
def _delete_rows(self) -> None: def _delete_rows(self) -> None:
""" """
@ -665,6 +663,18 @@ class PlaylistTab(QTableView):
model = cast(PlaylistModel, self.model()) model = cast(PlaylistModel, self.model())
model.delete_rows(self._get_selected_rows()) model.delete_rows(self._get_selected_rows())
def _get_selected_row(self) -> Optional[int]:
"""
Return row_number number of first selected row,
or None if none selected
"""
sm = self.selectionModel()
if sm:
if sm.hasSelection():
return sm.selectedIndexes()[0].row()
return None
def _get_selected_rows(self) -> List[int]: def _get_selected_rows(self) -> List[int]:
"""Return a list of selected row numbers sorted by row""" """Return a list of selected row numbers sorted by row"""
@ -729,13 +739,6 @@ class PlaylistTab(QTableView):
# else: # else:
# return # return
def _mark_as_unplayed(self, row_numbers: List[int]) -> None:
"""Rescan track"""
model = cast(PlaylistModel, self.model())
model.mark_unplayed(row_numbers)
self.clear_selection()
def _obs_change_scene(self, current_row: int) -> None: def _obs_change_scene(self, current_row: int) -> None:
""" """
Try to change OBS scene to the name passed Try to change OBS scene to the name passed
@ -1014,10 +1017,3 @@ class PlaylistTab(QTableView):
return return
self.setSpan(row, column, rowSpan, columnSpan) self.setSpan(row, column, rowSpan, columnSpan)
def _unmark_as_next(self) -> None:
"""Rescan track"""
model = cast(PlaylistModel, self.model())
model.set_next_row(None)
self.clear_selection()

View File

@ -75,13 +75,7 @@ def main():
continue continue
new_tags = get_tags(new_path) new_tags = get_tags(new_path)
new_title = new_tags["title"] new_title = new_tags["title"]
if not new_title:
print(f"{new_fname} does not have a title tag")
sys.exit(1)
new_artist = new_tags["artist"] new_artist = new_tags["artist"]
if not new_artist:
print(f"{new_fname} does not have an artist tag")
sys.exit(1)
bitrate = new_tags["bitrate"] bitrate = new_tags["bitrate"]
# If same filename exists in parent direcory, check tags # If same filename exists in parent direcory, check tags

59
poetry.lock generated
View File

@ -859,39 +859,39 @@ files = [
[[package]] [[package]]
name = "mypy" name = "mypy"
version = "1.7.0" version = "1.6.1"
description = "Optional static typing for Python" description = "Optional static typing for Python"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "mypy-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5da84d7bf257fd8f66b4f759a904fd2c5a765f70d8b52dde62b521972a0a2357"}, {file = "mypy-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c"},
{file = "mypy-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a3637c03f4025f6405737570d6cbfa4f1400eb3c649317634d273687a09ffc2f"}, {file = "mypy-1.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb"},
{file = "mypy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b633f188fc5ae1b6edca39dae566974d7ef4e9aaaae00bc36efe1f855e5173ac"}, {file = "mypy-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e"},
{file = "mypy-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed9a3997b90c6f891138e3f83fb8f475c74db4ccaa942a1c7bf99e83a989a1"}, {file = "mypy-1.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f"},
{file = "mypy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fe46e96ae319df21359c8db77e1aecac8e5949da4773c0274c0ef3d8d1268a9"}, {file = "mypy-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c"},
{file = "mypy-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:df67fbeb666ee8828f675fee724cc2cbd2e4828cc3df56703e02fe6a421b7401"}, {file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"},
{file = "mypy-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a79cdc12a02eb526d808a32a934c6fe6df07b05f3573d210e41808020aed8b5d"}, {file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"},
{file = "mypy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65f385a6f43211effe8c682e8ec3f55d79391f70a201575def73d08db68ead1"}, {file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"},
{file = "mypy-1.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e81ffd120ee24959b449b647c4b2fbfcf8acf3465e082b8d58fd6c4c2b27e46"}, {file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"},
{file = "mypy-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:f29386804c3577c83d76520abf18cfcd7d68264c7e431c5907d250ab502658ee"}, {file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"},
{file = "mypy-1.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87c076c174e2c7ef8ab416c4e252d94c08cd4980a10967754f91571070bf5fbe"}, {file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"},
{file = "mypy-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cb8d5f6d0fcd9e708bb190b224089e45902cacef6f6915481806b0c77f7786d"}, {file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"},
{file = "mypy-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93e76c2256aa50d9c82a88e2f569232e9862c9982095f6d54e13509f01222fc"}, {file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"},
{file = "mypy-1.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cddee95dea7990e2215576fae95f6b78a8c12f4c089d7e4367564704e99118d3"}, {file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"},
{file = "mypy-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:d01921dbd691c4061a3e2ecdbfbfad029410c5c2b1ee88946bf45c62c6c91210"}, {file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"},
{file = "mypy-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:185cff9b9a7fec1f9f7d8352dff8a4c713b2e3eea9c6c4b5ff7f0edf46b91e41"}, {file = "mypy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169"},
{file = "mypy-1.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7b1e399c47b18feb6f8ad4a3eef3813e28c1e871ea7d4ea5d444b2ac03c418"}, {file = "mypy-1.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143"},
{file = "mypy-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc9fe455ad58a20ec68599139ed1113b21f977b536a91b42bef3ffed5cce7391"}, {file = "mypy-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46"},
{file = "mypy-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d0fa29919d2e720c8dbaf07d5578f93d7b313c3e9954c8ec05b6d83da592e5d9"}, {file = "mypy-1.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85"},
{file = "mypy-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b53655a295c1ed1af9e96b462a736bf083adba7b314ae775563e3fb4e6795f5"}, {file = "mypy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45"},
{file = "mypy-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1b06b4b109e342f7dccc9efda965fc3970a604db70f8560ddfdee7ef19afb05"}, {file = "mypy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208"},
{file = "mypy-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf7a2f0a6907f231d5e41adba1a82d7d88cf1f61a70335889412dec99feeb0f8"}, {file = "mypy-1.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd"},
{file = "mypy-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551d4a0cdcbd1d2cccdcc7cb516bb4ae888794929f5b040bb51aae1846062901"}, {file = "mypy-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332"},
{file = "mypy-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55d28d7963bef00c330cb6461db80b0b72afe2f3c4e2963c99517cf06454e665"}, {file = "mypy-1.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f"},
{file = "mypy-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:870bd1ffc8a5862e593185a4c169804f2744112b4a7c55b93eb50f48e7a77010"}, {file = "mypy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30"},
{file = "mypy-1.7.0-py3-none-any.whl", hash = "sha256:96650d9a4c651bc2a4991cf46f100973f656d69edc7faf91844e87fe627f7e96"}, {file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"},
{file = "mypy-1.7.0.tar.gz", hash = "sha256:1e280b5697202efa698372d2f39e9a6713a0395a756b1c6bd48995f8d72690dc"}, {file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"},
] ]
[package.dependencies] [package.dependencies]
@ -902,7 +902,6 @@ typing-extensions = ">=4.1.0"
[package.extras] [package.extras]
dmypy = ["psutil (>=4.0)"] dmypy = ["psutil (>=4.0)"]
install-types = ["pip"] install-types = ["pip"]
mypyc = ["setuptools (>=50)"]
reports = ["lxml"] reports = ["lxml"]
[[package]] [[package]]
@ -2189,4 +2188,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "5bd0a9ae09f61079a0325639485adb206357cd5ea942944ccb5855f2a83d4db6" content-hash = "c822ba4ac16ffd05a4bfbcdb552aacc44e75d62deb4cfdba16771a8b13e14187"

View File

@ -41,7 +41,7 @@ sphinx = "^7.0.1"
furo = "^2023.5.20" furo = "^2023.5.20"
black = "^23.3.0" black = "^23.3.0"
flakehell = "^0.9.0" flakehell = "^0.9.0"
mypy = "^1.7.0" mypy = "^1.6.0"
pdbp = "^1.5.0" pdbp = "^1.5.0"
[build-system] [build-system]