Compare commits

..

7 Commits

Author SHA1 Message Date
Keith Edmunds
e3d20c9bdc WIP V3: cleanup 2023-11-20 11:24:12 +00:00
Keith Edmunds
5add1f01c6 WIP V3: use signals to open wikipedia/songfacts pages
Also open wikipedia page on selecting next track
2023-11-19 21:50:39 +00:00
Keith Edmunds
88e638a56e WIP V3: search wikipedia/songfacts from menu 2023-11-19 21:31:09 +00:00
Keith Edmunds
4ca5eb24c3 WIP V3: remove track from row implemented 2023-11-19 20:56:46 +00:00
Keith Edmunds
05ef2d766c WIP V3: Black 2023-11-19 20:49:50 +00:00
Keith Edmunds
db547cbdb7 WIP V3: import tracks working 2023-11-19 16:02:44 +00:00
Keith Edmunds
005d17ee0a Check for no title/artist tag in replace_files 2023-11-19 11:44:43 +00:00
9 changed files with 224 additions and 129 deletions

View File

@ -83,7 +83,11 @@ class MusicMusterSignals(QObject):
add_track_to_playlist_signal = pyqtSignal(int, int, int, str)
enable_escape_signal = pyqtSignal(bool)
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)
status_message_signal = pyqtSignal(str, int)
def __post_init__(self):
super().__init__()

View File

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

View File

@ -8,6 +8,8 @@ from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import QTabWidget
from config import Config
from classes import MusicMusterSignals
class InfoTabs(QTabWidget):
"""
@ -17,7 +19,9 @@ class InfoTabs(QTabWidget):
def __init__(self, parent=None) -> None:
super().__init__(parent)
# Dictionary to record when tabs were last updated (so we can
self.signals = MusicMusterSignals()
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)
self.last_update: Dict[QWebEngineView, datetime] = {}
self.tabtitles: Dict[int, str] = {}

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3
from datetime import datetime, timedelta
from os.path import basename
from time import sleep
from typing import (
cast,
@ -74,7 +73,7 @@ from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
from ui.main_window_ui import Ui_MainWindow # type: ignore
from utilities import check_db, update_bitrates
import helpers
import icons_rc # noqa F401
from ui import icons_rc # noqa F401
import music
@ -145,46 +144,6 @@ class CartButton(QPushButton):
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):
def __init__(self, parent=None, *args, **kwargs) -> None:
super().__init__(parent)
@ -476,7 +435,7 @@ class Window(QMainWindow, Ui_MainWindow):
closing_tab_playlist_id = self.tabPlaylist.widget(tab_index).playlist_id
if current_track_playlist_id:
if closing_tab_playlist_id == current_track_playlist_id:
self.statusbar.showMessage("Can't close current track playlist", 5000)
self.show_status_message("Can't close current track playlist", 5000)
return False
# Record playlist as closed and update remaining playlist tabs
@ -523,10 +482,10 @@ class Window(QMainWindow, Ui_MainWindow):
self.actionResume.triggered.connect(self.resume)
self.actionSave_as_template.triggered.connect(self.save_as_template)
self.actionSearch_title_in_Songfacts.triggered.connect(
lambda: self.tabPlaylist.currentWidget().lookup_row_in_songfacts()
self.lookup_row_in_songfacts
)
self.actionSearch_title_in_Wikipedia.triggered.connect(
lambda: self.tabPlaylist.currentWidget().lookup_row_in_wikipedia()
self.lookup_row_in_wikipedia
)
self.actionSearch.triggered.connect(self.search_playlist)
self.actionSelect_duplicate_rows.triggered.connect(
@ -551,6 +510,8 @@ class Window(QMainWindow, Ui_MainWindow):
self.signals.enable_escape_signal.connect(self.enable_escape)
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.timer500.timeout.connect(self.tick_500ms)
@ -638,7 +599,7 @@ class Window(QMainWindow, Ui_MainWindow):
"""
self.actionPlay_next.setEnabled(False)
self.statusbar.showMessage("Play controls: Disabled", 0)
self.show_status_message("Play controls: Disabled", 0)
def download_played_tracks(self) -> None:
"""Download a CSV of played tracks"""
@ -689,7 +650,7 @@ class Window(QMainWindow, Ui_MainWindow):
"""
self.actionPlay_next.setEnabled(True)
self.statusbar.showMessage("Play controls: Enabled", 0)
self.show_status_message("Play controls: Enabled", 0)
def export_playlist_tab(self) -> None:
"""Export the current playlist to an m3u file"""
@ -786,7 +747,21 @@ class Window(QMainWindow, Ui_MainWindow):
txt = ""
tags = helpers.get_tags(fname)
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"]
if not artist:
helpers.show_warning(
self,
"Problem with track file",
f"{fname} does not have an artist tag",
)
continue
count = 0
possible_matches = Tracks.search_titles(session, title)
if possible_matches:
@ -813,33 +788,10 @@ class Window(QMainWindow, Ui_MainWindow):
continue
new_tracks.append(fname)
# Import in separate thread
self.import_thread = QThread()
self.worker = ImportTrack(
self.active_tab(),
new_tracks,
self.active_tab().get_new_row_number(),
# Pass to model to manage the import
self.active_model().import_tracks(
new_tracks, self.active_tab().get_selected_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:
"""Show dialog box to enter header text and add to playlist"""
@ -887,6 +839,36 @@ class Window(QMainWindow, Ui_MainWindow):
if record.f_int and record.f_int >= 0:
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(
self, session: scoped_session, playlistrows: Sequence[PlaylistRows]
) -> None:
@ -1206,7 +1188,7 @@ class Window(QMainWindow, Ui_MainWindow):
playlist_id = self.active_tab().playlist_id
playlist = session.get(Playlists, playlist_id)
if playlist:
new_name = self.solicit_playlist_name(playlist.name)
new_name = self.solicit_playlist_name(session, playlist.name)
if new_name:
playlist.rename(session, new_name)
idx = self.tabBar.currentIndex()
@ -1355,6 +1337,14 @@ class Window(QMainWindow, Ui_MainWindow):
# self.tabPlaylist.setCurrentWidget(self.current_track.playlist_tab)
# 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:
"""Scroll to show next track"""
@ -1364,6 +1354,13 @@ class Window(QMainWindow, Ui_MainWindow):
# self.tabPlaylist.setCurrentWidget(self.next_track.playlist_tab)
# 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(
self, session: scoped_session, default: str = ""
) -> Optional[str]:

View File

@ -2,12 +2,16 @@ from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import auto, Enum
from operator import attrgetter
from os.path import basename
from typing import List, Optional
from PyQt6.QtCore import (
pyqtSignal,
QAbstractTableModel,
QModelIndex,
QObject,
Qt,
QThread,
QVariant,
)
from PyQt6.QtGui import (
@ -22,8 +26,10 @@ from dbconfig import scoped_session, Session
from helpers import (
file_is_unreadable,
get_embedded_time,
get_file_metadata,
get_relative_date,
open_in_audacity,
normalise_track,
ms_to_mmss,
set_track_metadata,
)
@ -150,7 +156,9 @@ class PlaylistModel(QAbstractTableModel):
if playlist_id != self.playlist_id:
return
self.insert_row(proposed_row_number=new_row_number, track_id=track_id, note=note)
self.insert_row(
proposed_row_number=new_row_number, track_id=track_id, note=note
)
def add_track_to_header(
self,
@ -634,6 +642,28 @@ class PlaylistModel(QAbstractTableModel):
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:
"""
Return True if row is a header row, else False
@ -824,11 +854,16 @@ class PlaylistModel(QAbstractTableModel):
def remove_track(self, row_number: int) -> None:
"""
Remove track from row
Remove track from row, retaining row as a header row
"""
# TODO
print(f"remove_track({row_number=})")
with Session() as session:
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
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:
"""
@ -882,6 +917,7 @@ class PlaylistModel(QAbstractTableModel):
return
track_sequence.next = PlaylistTrack()
self.invalidate_row(next_row_was)
self.signals.next_track_changed_signal.emit()
return
# Update playing_trtack
@ -905,6 +941,7 @@ class PlaylistModel(QAbstractTableModel):
return
track_sequence.next.set_plr(session, plr)
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.update_track_times()
@ -1079,3 +1116,46 @@ class PlaylistModel(QAbstractTableModel):
self.index(updated_row, Col.START_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
"""
selected_row = self._get_selected_row()
selected_row = self.get_selected_row_number()
if selected_row is None:
return
model = cast(PlaylistModel, self.model())
@ -541,13 +541,13 @@ class PlaylistTab(QTableView):
# Mark unplayed
if track_row and model.is_unplayed_row(row_number):
self._add_context_menu(
"Mark unplayed", lambda: model.mark_unplayed(self._get_selected_rows())
"Mark unplayed", lambda: self._mark_as_unplayed(self._get_selected_rows())
)
# Unmark as next
if next_row:
self._add_context_menu(
"Unmark as next track", lambda: model.set_next_row(None)
"Unmark as next track", lambda: self._unmark_as_next()
)
# ----------------------
@ -582,7 +582,7 @@ class PlaylistTab(QTableView):
# Track path TODO
if track_row:
self._add_context_menu("Copy track path", lambda: print("Track path"))
self._add_context_menu("Copy track path", lambda: self._copy_path(row_number))
def _calculate_end_time(
self, start: Optional[datetime], duration: int
@ -624,7 +624,8 @@ class PlaylistTab(QTableView):
to the clipboard. Otherwise, return None.
"""
track_path = self._get_row_track_path(row_number)
model = cast(PlaylistModel, self.model())
track_path = model.get_row_info(row_number).path
if not track_path:
return
@ -638,6 +639,7 @@ class PlaylistTab(QTableView):
track_path = track_path.replace(old, new)
cb = QApplication.clipboard()
if cb:
cb.clear(mode=cb.Mode.Clipboard)
cb.setText(track_path, mode=cb.Mode.Clipboard)
@ -663,18 +665,6 @@ class PlaylistTab(QTableView):
model = cast(PlaylistModel, self.model())
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]:
"""Return a list of selected row numbers sorted by row"""
@ -739,6 +729,13 @@ class PlaylistTab(QTableView):
# else:
# 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:
"""
Try to change OBS scene to the name passed
@ -1017,3 +1014,10 @@ class PlaylistTab(QTableView):
return
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,7 +75,13 @@ def main():
continue
new_tags = get_tags(new_path)
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"]
if not new_artist:
print(f"{new_fname} does not have an artist tag")
sys.exit(1)
bitrate = new_tags["bitrate"]
# If same filename exists in parent direcory, check tags

59
poetry.lock generated
View File

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

View File

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