Migrated to Alchemical
This commit is contained in:
parent
9d44642fea
commit
df620cde86
2
.envrc
2
.envrc
@ -15,6 +15,6 @@ elif on_git_branch master; then
|
||||
export MM_DB="mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_prod"
|
||||
else
|
||||
export MM_ENV="DEVELOPMENT"
|
||||
export MM_DB="mysql+mysqldb://dev_musicmuster:dev_musicmuster@localhost/dev_musicmuster"
|
||||
export ALCHEMICAL_DATABASE_URI="mysql+mysqldb://dev_musicmuster:dev_musicmuster@localhost/dev_musicmuster"
|
||||
export PYTHONBREAKPOINT="pudb.set_trace"
|
||||
fi
|
||||
|
||||
@ -35,7 +35,7 @@ class Config(object):
|
||||
COLOUR_WARNING_TIMER = "#ffc107"
|
||||
DBFS_SILENCE = -50
|
||||
DEBUG_FUNCTIONS: List[Optional[str]] = []
|
||||
DEBUG_MODULES: List[Optional[str]] = ["dbconfig"]
|
||||
DEBUG_MODULES: List[Optional[str]] = []
|
||||
DEFAULT_COLUMN_WIDTH = 200
|
||||
DISPLAY_SQL = False
|
||||
EPOCH = dt.datetime(1970, 1, 1)
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
# Standard library imports
|
||||
import os
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
import datetime as dt
|
||||
import os
|
||||
|
||||
# PyQt imports
|
||||
|
||||
@ -24,8 +25,6 @@ from sqlalchemy.orm import (
|
||||
|
||||
|
||||
# Database classes
|
||||
# Note: initialisation of the 'db' variable is at the foot of this
|
||||
# module.
|
||||
class CartsTable(Model):
|
||||
__tablename__ = "carts"
|
||||
|
||||
@ -56,7 +55,7 @@ class NoteColoursTable(Model):
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<NoteColour(id={self.id}, substring={self.substring}, "
|
||||
f"<NoteColours(id={self.id}, substring={self.substring}, "
|
||||
f"colour={self.colour}>"
|
||||
)
|
||||
|
||||
@ -67,7 +66,7 @@ class PlaydatesTable(Model):
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
lastplayed: Mapped[dt.datetime] = mapped_column(index=True)
|
||||
track_id: Mapped[int] = mapped_column(ForeignKey("tracks.id"))
|
||||
track: Mapped["TracksTable"] = relationship("Tracks", back_populates="playdates")
|
||||
track: Mapped["TracksTable"] = relationship("TracksTable", back_populates="playdates")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
@ -91,10 +90,10 @@ class PlaylistsTable(Model):
|
||||
is_template: Mapped[bool] = mapped_column(default=False)
|
||||
deleted: Mapped[bool] = mapped_column(default=False)
|
||||
rows: Mapped[List["PlaylistRowsTable"]] = relationship(
|
||||
"PlaylistRows",
|
||||
"PlaylistRowsTable",
|
||||
back_populates="playlist",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="PlaylistRows.plr_rownum",
|
||||
order_by="PlaylistRowsTable.plr_rownum",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@ -116,7 +115,7 @@ class PlaylistRowsTable(Model):
|
||||
playlist: Mapped[PlaylistsTable] = relationship(back_populates="rows")
|
||||
track_id: Mapped[Optional[int]] = mapped_column(ForeignKey("tracks.id"))
|
||||
track: Mapped["TracksTable"] = relationship(
|
||||
"Tracks",
|
||||
"TracksTable",
|
||||
back_populates="playlistrows",
|
||||
)
|
||||
played: Mapped[bool] = mapped_column(
|
||||
@ -163,11 +162,11 @@ class TracksTable(Model):
|
||||
silence_at: Mapped[int] = mapped_column(index=False)
|
||||
start_gap: Mapped[int] = mapped_column(index=False)
|
||||
playlistrows: Mapped[List[PlaylistRowsTable]] = relationship(
|
||||
"PlaylistRows", back_populates="track"
|
||||
"PlaylistRowsTable", back_populates="track"
|
||||
)
|
||||
playlists = association_proxy("playlistrows", "playlist")
|
||||
playdates: Mapped[List[PlaydatesTable]] = relationship(
|
||||
"Playdates",
|
||||
"PlaydatesTable",
|
||||
back_populates="track",
|
||||
lazy="joined",
|
||||
)
|
||||
@ -177,11 +176,3 @@ class TracksTable(Model):
|
||||
f"<Track(id={self.id}, title={self.title}, "
|
||||
f"artist={self.artist}, path={self.path}>"
|
||||
)
|
||||
|
||||
|
||||
MYSQL_CONNECT = os.environ.get("MM_DB")
|
||||
if MYSQL_CONNECT is None:
|
||||
raise ValueError("MYSQL_CONNECT is undefined")
|
||||
else:
|
||||
dbname = MYSQL_CONNECT.split("/")[-1]
|
||||
db = Alchemical(MYSQL_CONNECT)
|
||||
|
||||
113
app/models.py
113
app/models.py
@ -1,11 +1,14 @@
|
||||
# Standard library imports
|
||||
from typing import List, Optional, Sequence
|
||||
import datetime as dt
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
# PyQt imports
|
||||
|
||||
# Third party imports
|
||||
from alchemical import Alchemical # type:ignore
|
||||
from sqlalchemy import (
|
||||
bindparam,
|
||||
delete,
|
||||
@ -13,16 +16,10 @@ from sqlalchemy import (
|
||||
select,
|
||||
update,
|
||||
)
|
||||
from sqlalchemy.exc import (
|
||||
IntegrityError,
|
||||
)
|
||||
from sqlalchemy.orm import (
|
||||
joinedload,
|
||||
scoped_session,
|
||||
)
|
||||
from sqlalchemy.orm.exc import (
|
||||
NoResultFound,
|
||||
)
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
# App imports
|
||||
import dbtables
|
||||
@ -30,12 +27,21 @@ from config import Config
|
||||
from log import log
|
||||
|
||||
|
||||
# Establish database connection
|
||||
ALCHEMICAL_DATABASE_URI = os.environ.get("ALCHEMICAL_DATABASE_URI")
|
||||
if ALCHEMICAL_DATABASE_URI is None:
|
||||
raise ValueError("ALCHEMICAL_DATABASE_URI is undefined")
|
||||
if 'unittest' in sys.modules and 'sqlite' not in ALCHEMICAL_DATABASE_URI:
|
||||
raise ValueError("Unit tests running on non-Sqlite database")
|
||||
db = Alchemical(ALCHEMICAL_DATABASE_URI)
|
||||
|
||||
|
||||
# Database classes
|
||||
class Carts(dbtables.CartsTable):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session: scoped_session,
|
||||
session: Session,
|
||||
cart_number: int,
|
||||
name: str,
|
||||
duration: Optional[int] = None,
|
||||
@ -58,7 +64,7 @@ class NoteColours(dbtables.NoteColoursTable):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session: scoped_session,
|
||||
session: Session,
|
||||
substring: str,
|
||||
colour: str,
|
||||
enabled: bool = True,
|
||||
@ -77,7 +83,7 @@ class NoteColours(dbtables.NoteColoursTable):
|
||||
session.flush()
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, session: scoped_session) -> Sequence["NoteColours"]:
|
||||
def get_all(cls, session: Session) -> Sequence["NoteColours"]:
|
||||
"""
|
||||
Return all records
|
||||
"""
|
||||
@ -85,7 +91,7 @@ class NoteColours(dbtables.NoteColoursTable):
|
||||
return session.scalars(select(cls)).all()
|
||||
|
||||
@staticmethod
|
||||
def get_colour(session: scoped_session, text: str) -> Optional[str]:
|
||||
def get_colour(session: Session, text: str) -> Optional[str]:
|
||||
"""
|
||||
Parse text and return colour string if matched, else empty string
|
||||
"""
|
||||
@ -118,7 +124,7 @@ class NoteColours(dbtables.NoteColoursTable):
|
||||
|
||||
class Playdates(dbtables.PlaydatesTable):
|
||||
|
||||
def __init__(self, session: scoped_session, track_id: int) -> None:
|
||||
def __init__(self, session: Session, track_id: int) -> None:
|
||||
"""Record that track was played"""
|
||||
|
||||
self.lastplayed = dt.datetime.now()
|
||||
@ -127,7 +133,7 @@ class Playdates(dbtables.PlaydatesTable):
|
||||
session.commit()
|
||||
|
||||
@staticmethod
|
||||
def last_played(session: scoped_session, track_id: int) -> dt.datetime:
|
||||
def last_played(session: Session, track_id: int) -> dt.datetime:
|
||||
"""Return datetime track last played or None"""
|
||||
|
||||
last_played = session.execute(
|
||||
@ -145,7 +151,7 @@ class Playdates(dbtables.PlaydatesTable):
|
||||
return Config.EPOCH # pragma: no cover
|
||||
|
||||
@staticmethod
|
||||
def played_after(session: scoped_session, since: dt.datetime) -> Sequence["Playdates"]:
|
||||
def played_after(session: Session, since: dt.datetime) -> Sequence["Playdates"]:
|
||||
"""Return a list of Playdates objects since passed time"""
|
||||
|
||||
return session.scalars(
|
||||
@ -157,13 +163,13 @@ class Playdates(dbtables.PlaydatesTable):
|
||||
|
||||
class Playlists(dbtables.PlaylistsTable):
|
||||
|
||||
def __init__(self, session: scoped_session, name: str):
|
||||
def __init__(self, session: Session, name: str):
|
||||
self.name = name
|
||||
session.add(self)
|
||||
session.flush()
|
||||
|
||||
@staticmethod
|
||||
def clear_tabs(session: scoped_session, playlist_ids: List[int]) -> None:
|
||||
def clear_tabs(session: Session, playlist_ids: List[int]) -> None:
|
||||
"""
|
||||
Make all tab records NULL
|
||||
"""
|
||||
@ -183,7 +189,7 @@ class Playlists(dbtables.PlaylistsTable):
|
||||
|
||||
@classmethod
|
||||
def create_playlist_from_template(
|
||||
cls, session: scoped_session, template: "Playlists", playlist_name: str
|
||||
cls, session: Session, template: "Playlists", playlist_name: str
|
||||
) -> Optional["Playlists"]:
|
||||
"""Create a new playlist from template"""
|
||||
|
||||
@ -197,7 +203,7 @@ class Playlists(dbtables.PlaylistsTable):
|
||||
|
||||
return playlist
|
||||
|
||||
def delete(self, session: scoped_session) -> None:
|
||||
def delete(self, session: Session) -> None:
|
||||
"""
|
||||
Mark as deleted
|
||||
"""
|
||||
@ -206,7 +212,7 @@ class Playlists(dbtables.PlaylistsTable):
|
||||
session.flush()
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, session: scoped_session) -> Sequence["Playlists"]:
|
||||
def get_all(cls, session: Session) -> Sequence["Playlists"]:
|
||||
"""Returns a list of all playlists ordered by last use"""
|
||||
|
||||
return session.scalars(
|
||||
@ -216,7 +222,7 @@ class Playlists(dbtables.PlaylistsTable):
|
||||
).all()
|
||||
|
||||
@classmethod
|
||||
def get_all_templates(cls, session: scoped_session) -> Sequence["Playlists"]:
|
||||
def get_all_templates(cls, session: Session) -> Sequence["Playlists"]:
|
||||
"""Returns a list of all templates ordered by name"""
|
||||
|
||||
return session.scalars(
|
||||
@ -224,7 +230,7 @@ class Playlists(dbtables.PlaylistsTable):
|
||||
).all()
|
||||
|
||||
@classmethod
|
||||
def get_closed(cls, session: scoped_session) -> Sequence["Playlists"]:
|
||||
def get_closed(cls, session: Session) -> Sequence["Playlists"]:
|
||||
"""Returns a list of all closed playlists ordered by last use"""
|
||||
|
||||
return session.scalars(
|
||||
@ -238,7 +244,7 @@ class Playlists(dbtables.PlaylistsTable):
|
||||
).all()
|
||||
|
||||
@classmethod
|
||||
def get_open(cls, session: scoped_session) -> Sequence[Optional["Playlists"]]:
|
||||
def get_open(cls, session: Session) -> Sequence[Optional["Playlists"]]:
|
||||
"""
|
||||
Return a list of loaded playlists ordered by tab.
|
||||
"""
|
||||
@ -254,7 +260,7 @@ class Playlists(dbtables.PlaylistsTable):
|
||||
self.last_used = dt.datetime.now()
|
||||
|
||||
@staticmethod
|
||||
def name_is_available(session: scoped_session, name: str) -> bool:
|
||||
def name_is_available(session: Session, name: str) -> bool:
|
||||
"""
|
||||
Return True if no playlist of this name exists else false.
|
||||
"""
|
||||
@ -264,7 +270,7 @@ class Playlists(dbtables.PlaylistsTable):
|
||||
is None
|
||||
)
|
||||
|
||||
def rename(self, session: scoped_session, new_name: str) -> None:
|
||||
def rename(self, session: Session, new_name: str) -> None:
|
||||
"""
|
||||
Rename playlist
|
||||
"""
|
||||
@ -274,7 +280,7 @@ class Playlists(dbtables.PlaylistsTable):
|
||||
|
||||
@staticmethod
|
||||
def save_as_template(
|
||||
session: scoped_session, playlist_id: int, template_name: str
|
||||
session: Session, playlist_id: int, template_name: str
|
||||
) -> None:
|
||||
"""Save passed playlist as new template"""
|
||||
|
||||
@ -292,7 +298,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session: scoped_session,
|
||||
session: Session,
|
||||
playlist_id: int,
|
||||
row_number: int,
|
||||
note: str = "",
|
||||
@ -305,7 +311,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
self.plr_rownum = row_number
|
||||
self.note = note
|
||||
session.add(self)
|
||||
session.flush()
|
||||
session.commit()
|
||||
|
||||
def append_note(self, extra_note: str) -> None:
|
||||
"""Append passed note to any existing note"""
|
||||
@ -317,7 +323,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
self.note = extra_note
|
||||
|
||||
@staticmethod
|
||||
def copy_playlist(session: scoped_session, src_id: int, dst_id: int) -> None:
|
||||
def copy_playlist(session: Session, src_id: int, dst_id: int) -> None:
|
||||
"""Copy playlist entries"""
|
||||
|
||||
src_rows = session.scalars(
|
||||
@ -335,7 +341,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
|
||||
@classmethod
|
||||
def deep_row(
|
||||
cls, session: scoped_session, playlist_id: int, row_number: int
|
||||
cls, session: Session, playlist_id: int, row_number: int
|
||||
) -> "PlaylistRows":
|
||||
"""
|
||||
Return a playlist row that includes full track and lastplayed data for
|
||||
@ -356,7 +362,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
|
||||
@classmethod
|
||||
def deep_rows(
|
||||
cls, session: scoped_session, playlist_id: int
|
||||
cls, session: Session, playlist_id: int
|
||||
) -> Sequence["PlaylistRows"]:
|
||||
"""
|
||||
Return a list of playlist rows that include full track and lastplayed data for
|
||||
@ -375,7 +381,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
|
||||
@staticmethod
|
||||
def delete_higher_rows(
|
||||
session: scoped_session, playlist_id: int, maxrow: int
|
||||
session: Session, playlist_id: int, maxrow: int
|
||||
) -> None:
|
||||
"""
|
||||
Delete rows in given playlist that have a higher row number
|
||||
@ -391,7 +397,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
session.flush()
|
||||
|
||||
@staticmethod
|
||||
def delete_row(session: scoped_session, playlist_id: int, row_number: int) -> None:
|
||||
def delete_row(session: Session, playlist_id: int, row_number: int) -> None:
|
||||
"""
|
||||
Delete passed row in given playlist.
|
||||
"""
|
||||
@ -404,7 +410,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def fixup_rownumbers(session: scoped_session, playlist_id: int) -> None:
|
||||
def fixup_rownumbers(session: Session, playlist_id: int) -> None:
|
||||
"""
|
||||
Ensure the row numbers for passed playlist have no gaps
|
||||
"""
|
||||
@ -423,7 +429,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
|
||||
@classmethod
|
||||
def plrids_to_plrs(
|
||||
cls, session: scoped_session, playlist_id: int, plr_ids: List[int]
|
||||
cls, session: Session, playlist_id: int, plr_ids: List[int]
|
||||
) -> Sequence["PlaylistRows"]:
|
||||
"""
|
||||
Take a list of PlaylistRows ids and return a list of corresponding
|
||||
@ -439,7 +445,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
return plrs
|
||||
|
||||
@staticmethod
|
||||
def get_last_used_row(session: scoped_session, playlist_id: int) -> Optional[int]:
|
||||
def get_last_used_row(session: Session, playlist_id: int) -> Optional[int]:
|
||||
"""Return the last used row for playlist, or None if no rows"""
|
||||
|
||||
return session.execute(
|
||||
@ -450,7 +456,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
|
||||
@staticmethod
|
||||
def get_track_plr(
|
||||
session: scoped_session, track_id: int, playlist_id: int
|
||||
session: Session, track_id: int, playlist_id: int
|
||||
) -> Optional["PlaylistRows"]:
|
||||
"""Return first matching PlaylistRows object or None"""
|
||||
|
||||
@ -465,7 +471,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
|
||||
@classmethod
|
||||
def get_played_rows(
|
||||
cls, session: scoped_session, playlist_id: int
|
||||
cls, session: Session, playlist_id: int
|
||||
) -> Sequence["PlaylistRows"]:
|
||||
"""
|
||||
For passed playlist, return a list of rows that
|
||||
@ -483,7 +489,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
@classmethod
|
||||
def get_rows_with_tracks(
|
||||
cls,
|
||||
session: scoped_session,
|
||||
session: Session,
|
||||
playlist_id: int,
|
||||
) -> Sequence["PlaylistRows"]:
|
||||
"""
|
||||
@ -500,7 +506,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
|
||||
@classmethod
|
||||
def get_unplayed_rows(
|
||||
cls, session: scoped_session, playlist_id: int
|
||||
cls, session: Session, playlist_id: int
|
||||
) -> Sequence["PlaylistRows"]:
|
||||
"""
|
||||
For passed playlist, return a list of playlist rows that
|
||||
@ -521,14 +527,14 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
|
||||
@classmethod
|
||||
def insert_row(
|
||||
cls, session: scoped_session, playlist_id: int, new_row_number: int
|
||||
cls, session: Session, playlist_id: int, new_row_number: int
|
||||
) -> "PlaylistRows":
|
||||
cls.move_rows_down(session, playlist_id, new_row_number, 1)
|
||||
return cls(session, playlist_id, new_row_number)
|
||||
|
||||
@staticmethod
|
||||
def move_rows_down(
|
||||
session: scoped_session, playlist_id: int, starting_row: int, move_by: int
|
||||
session: Session, playlist_id: int, starting_row: int, move_by: int
|
||||
) -> None:
|
||||
"""
|
||||
Create space to insert move_by additional rows by incremented row
|
||||
@ -548,7 +554,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
|
||||
@staticmethod
|
||||
def update_plr_rownumbers(
|
||||
session: scoped_session, playlist_id: int, sqla_map: List[dict[str, int]]
|
||||
session: Session, playlist_id: int, sqla_map: List[dict[str, int]]
|
||||
) -> None:
|
||||
"""
|
||||
Take a {plrid: plr_rownum} dictionary and update the row numbers accordingly
|
||||
@ -569,6 +575,11 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
|
||||
|
||||
class Settings(dbtables.SettingsTable):
|
||||
|
||||
def __init__(self, session: Session, name: str):
|
||||
self.name = name
|
||||
session.add(self)
|
||||
session.flush()
|
||||
|
||||
@classmethod
|
||||
def all_as_dict(cls, session):
|
||||
"""
|
||||
@ -584,7 +595,7 @@ class Settings(dbtables.SettingsTable):
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_int_settings(cls, session: scoped_session, name: str) -> "Settings":
|
||||
def get_int_settings(cls, session: Session, name: str) -> "Settings":
|
||||
"""Get setting for an integer or return new setting record"""
|
||||
|
||||
try:
|
||||
@ -593,7 +604,7 @@ class Settings(dbtables.SettingsTable):
|
||||
except NoResultFound:
|
||||
return Settings(session, name)
|
||||
|
||||
def update(self, session: scoped_session, data: dict) -> None:
|
||||
def update(self, session: Session, data: dict) -> None:
|
||||
for key, value in data.items():
|
||||
assert hasattr(self, key)
|
||||
setattr(self, key, value)
|
||||
@ -610,7 +621,7 @@ class Tracks(dbtables.TracksTable):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session: scoped_session,
|
||||
session: Session,
|
||||
path: str,
|
||||
title: str,
|
||||
artist: str,
|
||||
@ -646,7 +657,7 @@ class Tracks(dbtables.TracksTable):
|
||||
return session.scalars(select(cls)).unique().all()
|
||||
|
||||
@classmethod
|
||||
def get_by_path(cls, session: scoped_session, path: str) -> Optional["Tracks"]:
|
||||
def get_by_path(cls, session: Session, path: str) -> Optional["Tracks"]:
|
||||
"""
|
||||
Return track with passed path, or None.
|
||||
"""
|
||||
@ -661,7 +672,7 @@ class Tracks(dbtables.TracksTable):
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def search_artists(cls, session: scoped_session, text: str) -> Sequence["Tracks"]:
|
||||
def search_artists(cls, session: Session, text: str) -> Sequence["Tracks"]:
|
||||
"""
|
||||
Search case-insenstively for artists containing str
|
||||
|
||||
@ -682,7 +693,7 @@ class Tracks(dbtables.TracksTable):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def search_titles(cls, session: scoped_session, text: str) -> Sequence["Tracks"]:
|
||||
def search_titles(cls, session: Session, text: str) -> Sequence["Tracks"]:
|
||||
"""
|
||||
Search case-insenstively for titles containing str
|
||||
|
||||
|
||||
@ -2,21 +2,14 @@
|
||||
# Allow forward reference to PlaylistModel
|
||||
from __future__ import annotations
|
||||
|
||||
# PyQt imports
|
||||
|
||||
# Third party imports
|
||||
|
||||
# App imports
|
||||
from dbtables import db
|
||||
|
||||
import obsws_python as obs # type: ignore
|
||||
import re
|
||||
import datetime as dt
|
||||
from enum import auto, Enum
|
||||
from operator import attrgetter
|
||||
from random import shuffle
|
||||
from typing import List, Optional
|
||||
import datetime as dt
|
||||
import re
|
||||
|
||||
# PyQt imports
|
||||
from PyQt6.QtCore import (
|
||||
QAbstractTableModel,
|
||||
QModelIndex,
|
||||
@ -32,6 +25,11 @@ from PyQt6.QtGui import (
|
||||
QFont,
|
||||
)
|
||||
|
||||
# Third party imports
|
||||
import obsws_python as obs # type: ignore
|
||||
# import snoop # type: ignore
|
||||
|
||||
# App imports
|
||||
from classes import track_sequence, MusicMusterSignals, PlaylistTrack
|
||||
from config import Config
|
||||
from helpers import (
|
||||
@ -42,7 +40,7 @@ from helpers import (
|
||||
set_track_metadata,
|
||||
)
|
||||
from log import log
|
||||
from models import NoteColours, Playdates, PlaylistRows, Tracks
|
||||
from models import db, NoteColours, Playdates, PlaylistRows, Tracks
|
||||
|
||||
|
||||
HEADER_NOTES_COLUMN = 1
|
||||
@ -560,7 +558,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
else:
|
||||
new_row_number = proposed_row_number
|
||||
|
||||
log.info(f"get_new_row_number() return: {new_row_number=}")
|
||||
log.debug(f"get_new_row_number() return: {new_row_number=}")
|
||||
return new_row_number
|
||||
|
||||
def get_row_info(self, row_number: int) -> PlaylistRowData:
|
||||
@ -758,7 +756,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
Insert a row.
|
||||
"""
|
||||
|
||||
log.info(f"insert_row({proposed_row_number=}, {track_id=}, {note=})")
|
||||
log.debug(f"insert_row({proposed_row_number=}, {track_id=}, {note=})")
|
||||
|
||||
new_row_number = self._get_new_row_number(proposed_row_number)
|
||||
|
||||
@ -1350,12 +1348,15 @@ class PlaylistModel(QAbstractTableModel):
|
||||
Update track start/end times in self.playlist_rows
|
||||
"""
|
||||
|
||||
log.info("update_track_times()")
|
||||
log.debug("update_track_times()")
|
||||
|
||||
next_start_time: Optional[dt.datetime] = None
|
||||
update_rows: List[int] = []
|
||||
playlist_length = len(self.playlist_rows)
|
||||
if not playlist_length:
|
||||
return
|
||||
|
||||
for row_number in range(len(self.playlist_rows)):
|
||||
for row_number in range(playlist_length):
|
||||
prd = self.playlist_rows[row_number]
|
||||
|
||||
# Reset start_time if this is the current row
|
||||
|
||||
49
conftest.py
49
conftest.py
@ -1,49 +0,0 @@
|
||||
# https://itnext.io/setting-up-transactional-tests-with-pytest-and-sqlalchemy-b2d726347629
|
||||
|
||||
import pytest
|
||||
import helpers
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
|
||||
from app.models import Base, Tracks
|
||||
|
||||
DB_CONNECTION = "mysql+mysqldb://musicmuster_testing:musicmuster_testing@localhost/dev_musicmuster_testing"
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def db_engine():
|
||||
engine = create_engine(DB_CONNECTION, isolation_level="READ COMMITTED")
|
||||
Base.metadata.create_all(engine)
|
||||
yield engine
|
||||
engine.dispose()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def session(db_engine):
|
||||
connection = db_engine.connect()
|
||||
transaction = connection.begin()
|
||||
sm = sessionmaker(bind=connection)
|
||||
session = scoped_session(sm)
|
||||
# print(f"PyTest SqlA: session acquired [{hex(id(session))}]")
|
||||
yield session
|
||||
# print(f" PyTest SqlA: session released and cleaned up [{hex(id(session))}]")
|
||||
session.remove()
|
||||
transaction.rollback()
|
||||
connection.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def track1(session):
|
||||
track_path = "testdata/isa.mp3"
|
||||
metadata = helpers.get_file_metadata(track_path)
|
||||
track = Tracks(session, **metadata)
|
||||
return track
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def track2(session):
|
||||
track_path = "testdata/mom.mp3"
|
||||
metadata = helpers.get_file_metadata(track_path)
|
||||
track = Tracks(session, **metadata)
|
||||
return track
|
||||
345
poetry.lock
generated
345
poetry.lock
generated
@ -108,34 +108,34 @@ lxml = ["lxml"]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "24.2.0"
|
||||
version = "24.3.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29"},
|
||||
{file = "black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430"},
|
||||
{file = "black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f"},
|
||||
{file = "black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a"},
|
||||
{file = "black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd"},
|
||||
{file = "black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2"},
|
||||
{file = "black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92"},
|
||||
{file = "black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23"},
|
||||
{file = "black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b"},
|
||||
{file = "black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9"},
|
||||
{file = "black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693"},
|
||||
{file = "black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982"},
|
||||
{file = "black-24.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4"},
|
||||
{file = "black-24.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218"},
|
||||
{file = "black-24.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0"},
|
||||
{file = "black-24.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"},
|
||||
{file = "black-24.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8"},
|
||||
{file = "black-24.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8"},
|
||||
{file = "black-24.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540"},
|
||||
{file = "black-24.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31"},
|
||||
{file = "black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6"},
|
||||
{file = "black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894"},
|
||||
{file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"},
|
||||
{file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"},
|
||||
{file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"},
|
||||
{file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"},
|
||||
{file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"},
|
||||
{file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"},
|
||||
{file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"},
|
||||
{file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"},
|
||||
{file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"},
|
||||
{file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"},
|
||||
{file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"},
|
||||
{file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"},
|
||||
{file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"},
|
||||
{file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"},
|
||||
{file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"},
|
||||
{file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"},
|
||||
{file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"},
|
||||
{file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"},
|
||||
{file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"},
|
||||
{file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"},
|
||||
{file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"},
|
||||
{file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -265,6 +265,21 @@ files = [
|
||||
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cheap-repr"
|
||||
version = "0.5.1"
|
||||
description = "Better version of repr/reprlib for short, cheap string representations."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "cheap_repr-0.5.1-py2.py3-none-any.whl", hash = "sha256:30096998aeb49367a4a153988d7a99dce9dc59bbdd4b19740da6b4f3f97cf2ff"},
|
||||
{file = "cheap_repr-0.5.1.tar.gz", hash = "sha256:31ec63b9d8394aa23d746c8376c8307f75f9fca0b983566b8bcf13cc661fe6dd"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["Django", "Django (<2)", "Django (<3)", "chainmap", "numpy (>=1.16.3)", "numpy (>=1.16.3,<1.17)", "numpy (>=1.16.3,<1.19)", "pandas (>=0.24.2)", "pandas (>=0.24.2,<0.25)", "pandas (>=0.24.2,<0.26)", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.7"
|
||||
@ -601,23 +616,23 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "7.0.1"
|
||||
version = "7.1.0"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"},
|
||||
{file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"},
|
||||
{file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"},
|
||||
{file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
perf = ["ipython"]
|
||||
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
|
||||
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
@ -965,39 +980,39 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.8.0"
|
||||
version = "1.9.0"
|
||||
description = "Optional static typing for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"},
|
||||
{file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"},
|
||||
{file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"},
|
||||
{file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"},
|
||||
{file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"},
|
||||
{file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"},
|
||||
{file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"},
|
||||
{file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"},
|
||||
{file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"},
|
||||
{file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"},
|
||||
{file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"},
|
||||
{file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"},
|
||||
{file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"},
|
||||
{file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"},
|
||||
{file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"},
|
||||
{file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"},
|
||||
{file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"},
|
||||
{file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"},
|
||||
{file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"},
|
||||
{file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"},
|
||||
{file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"},
|
||||
{file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"},
|
||||
{file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"},
|
||||
{file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"},
|
||||
{file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"},
|
||||
{file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"},
|
||||
{file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"},
|
||||
{file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"},
|
||||
{file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"},
|
||||
{file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"},
|
||||
{file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"},
|
||||
{file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"},
|
||||
{file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"},
|
||||
{file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"},
|
||||
{file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"},
|
||||
{file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"},
|
||||
{file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"},
|
||||
{file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"},
|
||||
{file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"},
|
||||
{file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"},
|
||||
{file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"},
|
||||
{file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"},
|
||||
{file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"},
|
||||
{file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"},
|
||||
{file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"},
|
||||
{file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"},
|
||||
{file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"},
|
||||
{file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"},
|
||||
{file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"},
|
||||
{file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"},
|
||||
{file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"},
|
||||
{file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"},
|
||||
{file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"},
|
||||
{file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1109,14 +1124,14 @@ dev = ["black", "isort", "pytest", "pytest-randomly"]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "23.2"
|
||||
version = "24.0"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
|
||||
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
||||
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
|
||||
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1484,16 +1499,16 @@ PyQt6-sip = ">=13.6,<14"
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6-qt6"
|
||||
version = "6.6.2"
|
||||
version = "6.6.3"
|
||||
description = "The subset of a Qt installation needed by PyQt6."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "PyQt6_Qt6-6.6.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:7ef446d3ffc678a8586ff6dc9f0d27caf4dff05dea02c353540d2f614386faf9"},
|
||||
{file = "PyQt6_Qt6-6.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b8363d88623342a72ac17da9127dc12f259bb3148796ea029762aa2d499778d9"},
|
||||
{file = "PyQt6_Qt6-6.6.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:8d7f674a4ec43ca00191e14945ca4129acbe37a2172ed9d08214ad58b170bc11"},
|
||||
{file = "PyQt6_Qt6-6.6.2-py3-none-win_amd64.whl", hash = "sha256:5a41fe9d53b9e29e9ec5c23f3c5949dba160f90ca313ee8b96b8ffe6a5059387"},
|
||||
{file = "PyQt6_Qt6-6.6.3-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:1674d161ea49a36e9146fd652e789d413a246cc2455ac8bf9c76902b4bd3b986"},
|
||||
{file = "PyQt6_Qt6-6.6.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:18fe1fbbc709dcff5c513e3cac7b1d7b630fb189e6d32a1601f193d73d326f42"},
|
||||
{file = "PyQt6_Qt6-6.6.3-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:6ae465dfcbb819dae5e18e8c96abba735b5bb2f16c066497dda4b7ca17c066ce"},
|
||||
{file = "PyQt6_Qt6-6.6.3-py3-none-win_amd64.whl", hash = "sha256:dbe509eccc579f8818b2b2e8ba93e27986facdd1d4d83ef1c7d9bd47cdf32651"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1548,32 +1563,32 @@ PyQt6-WebEngine-Qt6 = ">=6.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6-webengine-qt6"
|
||||
version = "6.6.2"
|
||||
version = "6.6.3"
|
||||
description = "The subset of a Qt installation needed by PyQt6-WebEngine."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "PyQt6_WebEngine_Qt6-6.6.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:27b1b6a6f4ea115b3dd300d2df906d542009d9eb0e62b05e6b7cb85dfe68e9c3"},
|
||||
{file = "PyQt6_WebEngine_Qt6-6.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2364dfa3a6e751ead71b7ba759081be677fcf1c6bbd8a2a2a250eb5f06432e8"},
|
||||
{file = "PyQt6_WebEngine_Qt6-6.6.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:3da4db9ddd984b647d0b79fa10fc6cf65364dfe283cd702b12cb7164be2307cd"},
|
||||
{file = "PyQt6_WebEngine_Qt6-6.6.2-py3-none-win_amd64.whl", hash = "sha256:5d6f3ae521115cee77fea22b0248e7b219995390b951b51e4d519aef9c304ca8"},
|
||||
{file = "PyQt6_WebEngine_Qt6-6.6.3-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:4ce545accc5a58d62bde7ce18253a70b3970c28a24c94642ec89537352c23974"},
|
||||
{file = "PyQt6_WebEngine_Qt6-6.6.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a82308115193a6f220d6310453d1edbe30f1a8ac32c01fc813865319a2199959"},
|
||||
{file = "PyQt6_WebEngine_Qt6-6.6.3-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:87f636e23e9c1a1326bf91d273da6bdfed2f42fcc243e527e7b0dbc4f39e70dd"},
|
||||
{file = "PyQt6_WebEngine_Qt6-6.6.3-py3-none-win_amd64.whl", hash = "sha256:3d3e81db62f166f5fbc24b28660fe81c1be4390282bfb9bb48111f32a6bd0f51"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyqtgraph"
|
||||
version = "0.13.3"
|
||||
version = "0.13.4"
|
||||
description = "Scientific Graphics and GUI Library for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pyqtgraph-0.13.3-py3-none-any.whl", hash = "sha256:fdcc04ac4b32a7bedf1bf3cf74cbb93ab3ba5687791712bbfa8d0712377d2f2b"},
|
||||
{file = "pyqtgraph-0.13.3.tar.gz", hash = "sha256:58108d8411c7054e0841d8b791ee85e101fc296b9b359c0e01dde38a98ff2ace"},
|
||||
{file = "pyqtgraph-0.13.4-py3-none-any.whl", hash = "sha256:1dc9a786aa43cd787114366058dc3b4b8cb96a0e318f334720c7e6cc6c285940"},
|
||||
{file = "pyqtgraph-0.13.4.tar.gz", hash = "sha256:67b0d371405c4fd5f35afecfeb37d4b73bc118f187c52a965ed68d62f59b67b3"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.20.0"
|
||||
numpy = ">=1.22.0"
|
||||
|
||||
[[package]]
|
||||
name = "pyreadline3"
|
||||
@ -1589,14 +1604,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.4.4"
|
||||
version = "8.1.1"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
|
||||
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
|
||||
{file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"},
|
||||
{file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1604,11 +1619,11 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||
pluggy = ">=1.4,<2.0"
|
||||
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
@ -1736,19 +1751,19 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "69.1.1"
|
||||
version = "69.2.0"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"},
|
||||
{file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"},
|
||||
{file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"},
|
||||
{file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||
|
||||
[[package]]
|
||||
@ -1763,6 +1778,28 @@ files = [
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snoop"
|
||||
version = "0.4.3"
|
||||
description = "Powerful debugging tools for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "snoop-0.4.3-py2.py3-none-any.whl", hash = "sha256:b7418581889ff78b29d9dc5ad4625c4c475c74755fb5cba82c693c6e32afadc0"},
|
||||
{file = "snoop-0.4.3.tar.gz", hash = "sha256:2e0930bb19ff0dbdaa6f5933f88e89ed5984210ea9f9de0e1d8231fa5c1c1f25"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
asttokens = "*"
|
||||
cheap-repr = ">=0.4.0"
|
||||
executing = "*"
|
||||
pygments = "*"
|
||||
six = "*"
|
||||
|
||||
[package.extras]
|
||||
tests = ["Django", "birdseye", "littleutils", "numpy (>=1.16.5)", "pandas (>=0.24.2)", "pprintpp", "prettyprinter", "pytest", "pytest-order", "pytest-order (<=0.11.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "snowballstemmer"
|
||||
version = "2.2.0"
|
||||
@ -1943,61 +1980,61 @@ test = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.27"
|
||||
version = "2.0.29"
|
||||
description = "Database Abstraction Library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d04e579e911562f1055d26dab1868d3e0bb905db3bccf664ee8ad109f035618a"},
|
||||
{file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa67d821c1fd268a5a87922ef4940442513b4e6c377553506b9db3b83beebbd8"},
|
||||
{file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c7a596d0be71b7baa037f4ac10d5e057d276f65a9a611c46970f012752ebf2d"},
|
||||
{file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954d9735ee9c3fa74874c830d089a815b7b48df6f6b6e357a74130e478dbd951"},
|
||||
{file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5cd20f58c29bbf2680039ff9f569fa6d21453fbd2fa84dbdb4092f006424c2e6"},
|
||||
{file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:03f448ffb731b48323bda68bcc93152f751436ad6037f18a42b7e16af9e91c07"},
|
||||
{file = "SQLAlchemy-2.0.27-cp310-cp310-win32.whl", hash = "sha256:d997c5938a08b5e172c30583ba6b8aad657ed9901fc24caf3a7152eeccb2f1b4"},
|
||||
{file = "SQLAlchemy-2.0.27-cp310-cp310-win_amd64.whl", hash = "sha256:eb15ef40b833f5b2f19eeae65d65e191f039e71790dd565c2af2a3783f72262f"},
|
||||
{file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c5bad7c60a392850d2f0fee8f355953abaec878c483dd7c3836e0089f046bf6"},
|
||||
{file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3012ab65ea42de1be81fff5fb28d6db893ef978950afc8130ba707179b4284a"},
|
||||
{file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbcd77c4d94b23e0753c5ed8deba8c69f331d4fd83f68bfc9db58bc8983f49cd"},
|
||||
{file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d177b7e82f6dd5e1aebd24d9c3297c70ce09cd1d5d37b43e53f39514379c029c"},
|
||||
{file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:680b9a36029b30cf063698755d277885d4a0eab70a2c7c6e71aab601323cba45"},
|
||||
{file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1306102f6d9e625cebaca3d4c9c8f10588735ef877f0360b5cdb4fdfd3fd7131"},
|
||||
{file = "SQLAlchemy-2.0.27-cp311-cp311-win32.whl", hash = "sha256:5b78aa9f4f68212248aaf8943d84c0ff0f74efc65a661c2fc68b82d498311fd5"},
|
||||
{file = "SQLAlchemy-2.0.27-cp311-cp311-win_amd64.whl", hash = "sha256:15e19a84b84528f52a68143439d0c7a3a69befcd4f50b8ef9b7b69d2628ae7c4"},
|
||||
{file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0de1263aac858f288a80b2071990f02082c51d88335a1db0d589237a3435fe71"},
|
||||
{file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce850db091bf7d2a1f2fdb615220b968aeff3849007b1204bf6e3e50a57b3d32"},
|
||||
{file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dfc936870507da96aebb43e664ae3a71a7b96278382bcfe84d277b88e379b18"},
|
||||
{file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4fbe6a766301f2e8a4519f4500fe74ef0a8509a59e07a4085458f26228cd7cc"},
|
||||
{file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4535c49d961fe9a77392e3a630a626af5baa967172d42732b7a43496c8b28876"},
|
||||
{file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0fb3bffc0ced37e5aa4ac2416f56d6d858f46d4da70c09bb731a246e70bff4d5"},
|
||||
{file = "SQLAlchemy-2.0.27-cp312-cp312-win32.whl", hash = "sha256:7f470327d06400a0aa7926b375b8e8c3c31d335e0884f509fe272b3c700a7254"},
|
||||
{file = "SQLAlchemy-2.0.27-cp312-cp312-win_amd64.whl", hash = "sha256:f9374e270e2553653d710ece397df67db9d19c60d2647bcd35bfc616f1622dcd"},
|
||||
{file = "SQLAlchemy-2.0.27-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e97cf143d74a7a5a0f143aa34039b4fecf11343eed66538610debc438685db4a"},
|
||||
{file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7b5a3e2120982b8b6bd1d5d99e3025339f7fb8b8267551c679afb39e9c7c7f1"},
|
||||
{file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e36aa62b765cf9f43a003233a8c2d7ffdeb55bc62eaa0a0380475b228663a38f"},
|
||||
{file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5ada0438f5b74c3952d916c199367c29ee4d6858edff18eab783b3978d0db16d"},
|
||||
{file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b1d9d1bfd96eef3c3faedb73f486c89e44e64e40e5bfec304ee163de01cf996f"},
|
||||
{file = "SQLAlchemy-2.0.27-cp37-cp37m-win32.whl", hash = "sha256:ca891af9f3289d24a490a5fde664ea04fe2f4984cd97e26de7442a4251bd4b7c"},
|
||||
{file = "SQLAlchemy-2.0.27-cp37-cp37m-win_amd64.whl", hash = "sha256:fd8aafda7cdff03b905d4426b714601c0978725a19efc39f5f207b86d188ba01"},
|
||||
{file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec1f5a328464daf7a1e4e385e4f5652dd9b1d12405075ccba1df842f7774b4fc"},
|
||||
{file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ad862295ad3f644e3c2c0d8b10a988e1600d3123ecb48702d2c0f26771f1c396"},
|
||||
{file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48217be1de7d29a5600b5c513f3f7664b21d32e596d69582be0a94e36b8309cb"},
|
||||
{file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e56afce6431450442f3ab5973156289bd5ec33dd618941283847c9fd5ff06bf"},
|
||||
{file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:611068511b5531304137bcd7fe8117c985d1b828eb86043bd944cebb7fae3910"},
|
||||
{file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b86abba762ecfeea359112b2bb4490802b340850bbee1948f785141a5e020de8"},
|
||||
{file = "SQLAlchemy-2.0.27-cp38-cp38-win32.whl", hash = "sha256:30d81cc1192dc693d49d5671cd40cdec596b885b0ce3b72f323888ab1c3863d5"},
|
||||
{file = "SQLAlchemy-2.0.27-cp38-cp38-win_amd64.whl", hash = "sha256:120af1e49d614d2525ac247f6123841589b029c318b9afbfc9e2b70e22e1827d"},
|
||||
{file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d07ee7793f2aeb9b80ec8ceb96bc8cc08a2aec8a1b152da1955d64e4825fcbac"},
|
||||
{file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb0845e934647232b6ff5150df37ceffd0b67b754b9fdbb095233deebcddbd4a"},
|
||||
{file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fc19ae2e07a067663dd24fca55f8ed06a288384f0e6e3910420bf4b1270cc51"},
|
||||
{file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b90053be91973a6fb6020a6e44382c97739736a5a9d74e08cc29b196639eb979"},
|
||||
{file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2f5c9dfb0b9ab5e3a8a00249534bdd838d943ec4cfb9abe176a6c33408430230"},
|
||||
{file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33e8bde8fff203de50399b9039c4e14e42d4d227759155c21f8da4a47fc8053c"},
|
||||
{file = "SQLAlchemy-2.0.27-cp39-cp39-win32.whl", hash = "sha256:d873c21b356bfaf1589b89090a4011e6532582b3a8ea568a00e0c3aab09399dd"},
|
||||
{file = "SQLAlchemy-2.0.27-cp39-cp39-win_amd64.whl", hash = "sha256:ff2f1b7c963961d41403b650842dc2039175b906ab2093635d8319bef0b7d620"},
|
||||
{file = "SQLAlchemy-2.0.27-py3-none-any.whl", hash = "sha256:1ab4e0448018d01b142c916cc7119ca573803a4745cfe341b8f95657812700ac"},
|
||||
{file = "SQLAlchemy-2.0.27.tar.gz", hash = "sha256:86a6ed69a71fe6b88bf9331594fa390a2adda4a49b5c06f98e47bf0d392534f8"},
|
||||
{file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b"},
|
||||
{file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7"},
|
||||
{file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef3fbccb4058355053c51b82fd3501a6e13dd808c8d8cd2561e610c5456013c"},
|
||||
{file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d6753305936eddc8ed190e006b7bb33a8f50b9854823485eed3a886857ab8d1"},
|
||||
{file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0f3ca96af060a5250a8ad5a63699180bc780c2edf8abf96c58af175921df847a"},
|
||||
{file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4520047006b1d3f0d89e0532978c0688219857eb2fee7c48052560ae76aca1e"},
|
||||
{file = "SQLAlchemy-2.0.29-cp310-cp310-win32.whl", hash = "sha256:b2a0e3cf0caac2085ff172c3faacd1e00c376e6884b5bc4dd5b6b84623e29e4f"},
|
||||
{file = "SQLAlchemy-2.0.29-cp310-cp310-win_amd64.whl", hash = "sha256:01d10638a37460616708062a40c7b55f73e4d35eaa146781c683e0fa7f6c43fb"},
|
||||
{file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:308ef9cb41d099099fffc9d35781638986870b29f744382904bf9c7dadd08513"},
|
||||
{file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:296195df68326a48385e7a96e877bc19aa210e485fa381c5246bc0234c36c78e"},
|
||||
{file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13b917b4ffe5a0a31b83d051d60477819ddf18276852ea68037a144a506efb9"},
|
||||
{file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f6d971255d9ddbd3189e2e79d743ff4845c07f0633adfd1de3f63d930dbe673"},
|
||||
{file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:61405ea2d563407d316c63a7b5271ae5d274a2a9fbcd01b0aa5503635699fa1e"},
|
||||
{file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de7202ffe4d4a8c1e3cde1c03e01c1a3772c92858837e8f3879b497158e4cb44"},
|
||||
{file = "SQLAlchemy-2.0.29-cp311-cp311-win32.whl", hash = "sha256:b5d7ed79df55a731749ce65ec20d666d82b185fa4898430b17cb90c892741520"},
|
||||
{file = "SQLAlchemy-2.0.29-cp311-cp311-win_amd64.whl", hash = "sha256:205f5a2b39d7c380cbc3b5dcc8f2762fb5bcb716838e2d26ccbc54330775b003"},
|
||||
{file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d96710d834a6fb31e21381c6d7b76ec729bd08c75a25a5184b1089141356171f"},
|
||||
{file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:52de4736404e53c5c6a91ef2698c01e52333988ebdc218f14c833237a0804f1b"},
|
||||
{file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c7b02525ede2a164c5fa5014915ba3591730f2cc831f5be9ff3b7fd3e30958e"},
|
||||
{file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dfefdb3e54cd15f5d56fd5ae32f1da2d95d78319c1f6dfb9bcd0eb15d603d5d"},
|
||||
{file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a88913000da9205b13f6f195f0813b6ffd8a0c0c2bd58d499e00a30eb508870c"},
|
||||
{file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fecd5089c4be1bcc37c35e9aa678938d2888845a134dd016de457b942cf5a758"},
|
||||
{file = "SQLAlchemy-2.0.29-cp312-cp312-win32.whl", hash = "sha256:8197d6f7a3d2b468861ebb4c9f998b9df9e358d6e1cf9c2a01061cb9b6cf4e41"},
|
||||
{file = "SQLAlchemy-2.0.29-cp312-cp312-win_amd64.whl", hash = "sha256:9b19836ccca0d321e237560e475fd99c3d8655d03da80c845c4da20dda31b6e1"},
|
||||
{file = "SQLAlchemy-2.0.29-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87a1d53a5382cdbbf4b7619f107cc862c1b0a4feb29000922db72e5a66a5ffc0"},
|
||||
{file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0732dffe32333211801b28339d2a0babc1971bc90a983e3035e7b0d6f06b93"},
|
||||
{file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90453597a753322d6aa770c5935887ab1fc49cc4c4fdd436901308383d698b4b"},
|
||||
{file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ea311d4ee9a8fa67f139c088ae9f905fcf0277d6cd75c310a21a88bf85e130f5"},
|
||||
{file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f20cb0a63a3e0ec4e169aa8890e32b949c8145983afa13a708bc4b0a1f30e03"},
|
||||
{file = "SQLAlchemy-2.0.29-cp37-cp37m-win32.whl", hash = "sha256:e5bbe55e8552019c6463709b39634a5fc55e080d0827e2a3a11e18eb73f5cdbd"},
|
||||
{file = "SQLAlchemy-2.0.29-cp37-cp37m-win_amd64.whl", hash = "sha256:c2f9c762a2735600654c654bf48dad388b888f8ce387b095806480e6e4ff6907"},
|
||||
{file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e614d7a25a43a9f54fcce4675c12761b248547f3d41b195e8010ca7297c369c"},
|
||||
{file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:471fcb39c6adf37f820350c28aac4a7df9d3940c6548b624a642852e727ea586"},
|
||||
{file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:988569c8732f54ad3234cf9c561364221a9e943b78dc7a4aaf35ccc2265f1930"},
|
||||
{file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dddaae9b81c88083e6437de95c41e86823d150f4ee94bf24e158a4526cbead01"},
|
||||
{file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:334184d1ab8f4c87f9652b048af3f7abea1c809dfe526fb0435348a6fef3d380"},
|
||||
{file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:38b624e5cf02a69b113c8047cf7f66b5dfe4a2ca07ff8b8716da4f1b3ae81567"},
|
||||
{file = "SQLAlchemy-2.0.29-cp38-cp38-win32.whl", hash = "sha256:bab41acf151cd68bc2b466deae5deeb9e8ae9c50ad113444151ad965d5bf685b"},
|
||||
{file = "SQLAlchemy-2.0.29-cp38-cp38-win_amd64.whl", hash = "sha256:52c8011088305476691b8750c60e03b87910a123cfd9ad48576d6414b6ec2a1d"},
|
||||
{file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3071ad498896907a5ef756206b9dc750f8e57352113c19272bdfdc429c7bd7de"},
|
||||
{file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dba622396a3170974f81bad49aacebd243455ec3cc70615aeaef9e9613b5bca5"},
|
||||
{file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b184e3de58009cc0bf32e20f137f1ec75a32470f5fede06c58f6c355ed42a72"},
|
||||
{file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c37f1050feb91f3d6c32f864d8e114ff5545a4a7afe56778d76a9aec62638ba"},
|
||||
{file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bda7ce59b06d0f09afe22c56714c65c957b1068dee3d5e74d743edec7daba552"},
|
||||
{file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:25664e18bef6dc45015b08f99c63952a53a0a61f61f2e48a9e70cec27e55f699"},
|
||||
{file = "SQLAlchemy-2.0.29-cp39-cp39-win32.whl", hash = "sha256:77d29cb6c34b14af8a484e831ab530c0f7188f8efed1c6a833a2c674bf3c26ec"},
|
||||
{file = "SQLAlchemy-2.0.29-cp39-cp39-win_amd64.whl", hash = "sha256:04c487305ab035a9548f573763915189fc0fe0824d9ba28433196f8436f1449c"},
|
||||
{file = "SQLAlchemy-2.0.29-py3-none-any.whl", hash = "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305"},
|
||||
{file = "SQLAlchemy-2.0.29.tar.gz", hash = "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -2051,14 +2088,14 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
|
||||
|
||||
[[package]]
|
||||
name = "stackprinter"
|
||||
version = "0.2.11"
|
||||
version = "0.2.12"
|
||||
description = "Debug-friendly stack traces, with variable values and semantic highlighting"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.4"
|
||||
files = [
|
||||
{file = "stackprinter-0.2.11-py3-none-any.whl", hash = "sha256:101da55db7dfd54af516e3e209db9c84645285e5ea00d0b0709418dde2f157a1"},
|
||||
{file = "stackprinter-0.2.11.tar.gz", hash = "sha256:abbd8f4f892f24a5bd370119af49c3e3408b0bf04cd4d28e99f81c4e781a767b"},
|
||||
{file = "stackprinter-0.2.12-py3-none-any.whl", hash = "sha256:0a0623d46a5babd7a8a9787f605f4dd4a42d6ff7aee140541d5e9291a506e8d9"},
|
||||
{file = "stackprinter-0.2.12.tar.gz", hash = "sha256:271efc75ebdcc1554e58168ea7779f98066d54a325f57c7dc19f10fa998ef01e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2144,30 +2181,30 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "traitlets"
|
||||
version = "5.14.1"
|
||||
version = "5.14.2"
|
||||
description = "Traitlets Python configuration system"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"},
|
||||
{file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"},
|
||||
{file = "traitlets-5.14.2-py3-none-any.whl", hash = "sha256:fcdf85684a772ddeba87db2f398ce00b40ff550d1528c03c14dbf6a02003cd80"},
|
||||
{file = "traitlets-5.14.2.tar.gz", hash = "sha256:8cdd83c040dab7d1dee822678e5f5d100b514f7b72b01615b26fc5718916fdf9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
|
||||
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"]
|
||||
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.1)", "pytest-mock", "pytest-mypy-testing"]
|
||||
|
||||
[[package]]
|
||||
name = "types-psutil"
|
||||
version = "5.9.5.20240205"
|
||||
version = "5.9.5.20240316"
|
||||
description = "Typing stubs for psutil"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-psutil-5.9.5.20240205.tar.gz", hash = "sha256:51df36a361aa597bf483dcc5b58f2ab7aa87452a36d2da97c90994d6a81ef743"},
|
||||
{file = "types_psutil-5.9.5.20240205-py3-none-any.whl", hash = "sha256:3ec9bd8b95a64fe1269241d3ffb74b94a45df2d0391da1402423cd33f29745ca"},
|
||||
{file = "types-psutil-5.9.5.20240316.tar.gz", hash = "sha256:5636f5714bb930c64bb34c4d47a59dc92f9d610b778b5364a31daa5584944848"},
|
||||
{file = "types_psutil-5.9.5.20240316-py3-none-any.whl", hash = "sha256:2fdd64ea6e97befa546938f486732624f9255fde198b55e6f00fda236f059f64"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2202,14 +2239,14 @@ zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "urwid"
|
||||
version = "2.6.7"
|
||||
version = "2.6.10"
|
||||
description = "A full-featured console (xterm et al.) user interface library"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">3.7"
|
||||
files = [
|
||||
{file = "urwid-2.6.7-py3-none-any.whl", hash = "sha256:80b922d2051db6abe598b7e1b0b31d8d04fcc56d35bb1ec40b3c128fa0bd23ab"},
|
||||
{file = "urwid-2.6.7.tar.gz", hash = "sha256:597fa2d19ac788e4607d2a48aca32f257342201cb55e5f6a00a8fcd24e62a5ab"},
|
||||
{file = "urwid-2.6.10-py3-none-any.whl", hash = "sha256:f5d290ab01a9cf69a062d5d04ff69111903d41fc14ed03f3ed92cb36f5ef4735"},
|
||||
{file = "urwid-2.6.10.tar.gz", hash = "sha256:ae33355c414c13214e541d3634f3c8a0bfb373914e62ffbcf2fa863527706321"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -2274,21 +2311,21 @@ test = ["websockets"]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.17.0"
|
||||
version = "3.18.1"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
|
||||
{file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
|
||||
{file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"},
|
||||
{file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "f4fb2696ae984283c4c0d7816ba7cbd7be714695d6eb3c84b5da62b3809f9c82"
|
||||
content-hash = "e8a4a3f4b5dd70bd5fb2ab420b4de6e3304a15be383233bb01b966e047700cd1"
|
||||
|
||||
@ -31,7 +31,6 @@ alchemical = "^1.0.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
ipdb = "^0.13.9"
|
||||
pytest = "^7.0.1"
|
||||
pytest-qt = "^4.0.2"
|
||||
pydub-stubs = "^0.25.1"
|
||||
line-profiler = "^4.0.2"
|
||||
@ -46,6 +45,8 @@ flakehell = "^0.9.0"
|
||||
mypy = "^1.7.0"
|
||||
pdbp = "^1.5.0"
|
||||
pytest-cov = "^5.0.0"
|
||||
pytest = "^8.1.1"
|
||||
snoop = "^0.4.3"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
|
||||
@ -1,4 +1,12 @@
|
||||
# Standard library imports
|
||||
import datetime as dt
|
||||
import unittest
|
||||
|
||||
# PyQt imports
|
||||
|
||||
# Third party imports
|
||||
|
||||
# App imports
|
||||
from helpers import (
|
||||
fade_point,
|
||||
get_audio_segment,
|
||||
@ -9,68 +17,71 @@ from helpers import (
|
||||
)
|
||||
|
||||
|
||||
def test_fade_point():
|
||||
test_track_path = "testdata/isa.mp3"
|
||||
test_track_data = "testdata/isa.py"
|
||||
class TestMMHelpers(unittest.TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
audio_segment = get_audio_segment(test_track_path)
|
||||
assert audio_segment
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
fade_at = fade_point(audio_segment)
|
||||
def test_fade_point(self):
|
||||
test_track_path = "testdata/isa.mp3"
|
||||
test_track_data = "testdata/isa.py"
|
||||
|
||||
# Get test data
|
||||
with open(test_track_data) as f:
|
||||
testdata = eval(f.read())
|
||||
audio_segment = get_audio_segment(test_track_path)
|
||||
assert audio_segment
|
||||
|
||||
# Volume detection can vary, so ± 1 second is OK
|
||||
assert fade_at < testdata["fade_at"] + 1000
|
||||
assert fade_at > testdata["fade_at"] - 1000
|
||||
fade_at = fade_point(audio_segment)
|
||||
|
||||
# Get test data
|
||||
with open(test_track_data) as f:
|
||||
testdata = eval(f.read())
|
||||
|
||||
def test_get_tags():
|
||||
test_track_path = "testdata/mom.mp3"
|
||||
test_track_data = "testdata/mom.py"
|
||||
# Volume detection can vary, so ± 1 second is OK
|
||||
assert fade_at < testdata["fade_at"] + 1000
|
||||
assert fade_at > testdata["fade_at"] - 1000
|
||||
|
||||
tags = get_tags(test_track_path)
|
||||
def test_get_tags(self):
|
||||
test_track_path = "testdata/mom.mp3"
|
||||
test_track_data = "testdata/mom.py"
|
||||
|
||||
# Get test data
|
||||
with open(test_track_data) as f:
|
||||
testdata = eval(f.read())
|
||||
tags = get_tags(test_track_path)
|
||||
|
||||
assert tags["artist"] == testdata["artist"]
|
||||
assert tags["title"] == testdata["title"]
|
||||
# Get test data
|
||||
with open(test_track_data) as f:
|
||||
testdata = eval(f.read())
|
||||
|
||||
assert tags["artist"] == testdata["artist"]
|
||||
assert tags["title"] == testdata["title"]
|
||||
|
||||
def test_get_relative_date():
|
||||
assert get_relative_date(None) == "Never"
|
||||
today_at_10 = dt.datetime.now().replace(hour=10, minute=0)
|
||||
today_at_11 = dt.datetime.now().replace(hour=11, minute=0)
|
||||
assert get_relative_date(today_at_10, today_at_11) == "Today 10:00"
|
||||
eight_days_ago = today_at_10 - dt.timedelta(days=8)
|
||||
assert get_relative_date(eight_days_ago, today_at_11) == "1 week, 1 day ago"
|
||||
sixteen_days_ago = today_at_10 - dt.timedelta(days=16)
|
||||
assert get_relative_date(sixteen_days_ago, today_at_11) == "2 weeks, 2 days ago"
|
||||
def test_get_relative_date(self):
|
||||
assert get_relative_date(None) == "Never"
|
||||
today_at_10 = dt.datetime.now().replace(hour=10, minute=0)
|
||||
today_at_11 = dt.datetime.now().replace(hour=11, minute=0)
|
||||
assert get_relative_date(today_at_10, today_at_11) == "Today 10:00"
|
||||
eight_days_ago = today_at_10 - dt.timedelta(days=8)
|
||||
assert get_relative_date(eight_days_ago, today_at_11) == "1 week, 1 day ago"
|
||||
sixteen_days_ago = today_at_10 - dt.timedelta(days=16)
|
||||
assert get_relative_date(sixteen_days_ago, today_at_11) == "2 weeks, 2 days ago"
|
||||
|
||||
def test_leading_silence(self):
|
||||
test_track_path = "testdata/isa.mp3"
|
||||
test_track_data = "testdata/isa.py"
|
||||
|
||||
def test_leading_silence():
|
||||
test_track_path = "testdata/isa.mp3"
|
||||
test_track_data = "testdata/isa.py"
|
||||
audio_segment = get_audio_segment(test_track_path)
|
||||
assert audio_segment
|
||||
|
||||
audio_segment = get_audio_segment(test_track_path)
|
||||
assert audio_segment
|
||||
silence_at = leading_silence(audio_segment)
|
||||
|
||||
silence_at = leading_silence(audio_segment)
|
||||
# Get test data
|
||||
with open(test_track_data) as f:
|
||||
testdata = eval(f.read())
|
||||
|
||||
# Get test data
|
||||
with open(test_track_data) as f:
|
||||
testdata = eval(f.read())
|
||||
# Volume detection can vary, so ± 1 second is OK
|
||||
assert silence_at < testdata["leading_silence"] + 1000
|
||||
assert silence_at > testdata["leading_silence"] - 1000
|
||||
|
||||
# Volume detection can vary, so ± 1 second is OK
|
||||
assert silence_at < testdata["leading_silence"] + 1000
|
||||
assert silence_at > testdata["leading_silence"] - 1000
|
||||
|
||||
|
||||
def test_ms_to_mmss():
|
||||
assert ms_to_mmss(None) == "-"
|
||||
assert ms_to_mmss(59600) == "0:59"
|
||||
assert ms_to_mmss((5 * 60 * 1000) + 23000) == "5:23"
|
||||
def test_ms_to_mmss(self):
|
||||
assert ms_to_mmss(None) == "-"
|
||||
assert ms_to_mmss(59600) == "0:59"
|
||||
assert ms_to_mmss((5 * 60 * 1000) + 23000) == "5:23"
|
||||
|
||||
@ -1,25 +1,50 @@
|
||||
# Standard library imports
|
||||
import os
|
||||
import unittest
|
||||
|
||||
# PyQt imports
|
||||
|
||||
# Third party imports
|
||||
import pytest
|
||||
from models import NoteColours, Settings
|
||||
|
||||
# App imports
|
||||
# Set up test database before importing db
|
||||
# Mark subsequent lines to ignore E402, imports not at top of file
|
||||
# Set up test database before importing db
|
||||
# Mark subsequent lines to ignore E402, imports not at top of file
|
||||
DB_FILE = '/tmp/mm.db'
|
||||
if os.path.exists(DB_FILE):
|
||||
os.unlink(DB_FILE)
|
||||
os.environ['ALCHEMICAL_DATABASE_URI'] = 'sqlite:///' + DB_FILE
|
||||
from models import db, Settings # noqa: E402
|
||||
|
||||
|
||||
def test_log_exception():
|
||||
"""Test deliberate exception"""
|
||||
class TestMMMisc(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.create_all()
|
||||
|
||||
with pytest.raises(Exception):
|
||||
1 / 0
|
||||
def tearDown(self):
|
||||
db.drop_all()
|
||||
|
||||
def test_log_exception(self):
|
||||
"""Test deliberate exception"""
|
||||
|
||||
def test_create_settings(session):
|
||||
SETTING_NAME = "wombat"
|
||||
NO_SUCH_SETTING = "abc"
|
||||
VALUE = 3
|
||||
with pytest.raises(Exception):
|
||||
1 / 0
|
||||
|
||||
setting = Settings(session, SETTING_NAME)
|
||||
setting.update(session, dict(f_int=VALUE))
|
||||
print(setting)
|
||||
_ = Settings.all_as_dict(session)
|
||||
test = Settings.get_int_settings(session, SETTING_NAME)
|
||||
assert test.name == SETTING_NAME
|
||||
assert test.f_int == VALUE
|
||||
test_new = Settings.get_int_settings(session, NO_SUCH_SETTING)
|
||||
assert test_new.name == NO_SUCH_SETTING
|
||||
def test_create_settings(self):
|
||||
SETTING_NAME = "wombat"
|
||||
NO_SUCH_SETTING = "abc"
|
||||
VALUE = 3
|
||||
|
||||
with db.Session() as session:
|
||||
setting = Settings(session, SETTING_NAME)
|
||||
# test repr
|
||||
_ = str(setting)
|
||||
setting.update(session, dict(f_int=VALUE))
|
||||
_ = Settings.all_as_dict(session)
|
||||
test = Settings.get_int_settings(session, SETTING_NAME)
|
||||
assert test.name == SETTING_NAME
|
||||
assert test.f_int == VALUE
|
||||
test_new = Settings.get_int_settings(session, NO_SUCH_SETTING)
|
||||
assert test_new.name == NO_SUCH_SETTING
|
||||
|
||||
@ -1,6 +1,23 @@
|
||||
# Standard library imports
|
||||
import datetime as dt
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from app.models import (
|
||||
# PyQt imports
|
||||
|
||||
# Third party imports
|
||||
|
||||
# App imports
|
||||
from app import helpers
|
||||
|
||||
# Set up test database before importing db
|
||||
# Mark subsequent lines to ignore E402, imports not at top of file
|
||||
DB_FILE = '/tmp/mm.db'
|
||||
if os.path.exists(DB_FILE):
|
||||
os.unlink(DB_FILE)
|
||||
os.environ['ALCHEMICAL_DATABASE_URI'] = 'sqlite:///' + DB_FILE
|
||||
from app.models import ( # noqa: E402
|
||||
db,
|
||||
Carts,
|
||||
NoteColours,
|
||||
Playdates,
|
||||
@ -10,241 +27,275 @@ from app.models import (
|
||||
)
|
||||
|
||||
|
||||
def test_notecolours_get_colour(session):
|
||||
"""Create a colour record and retrieve all colours"""
|
||||
class TestMMModels(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.create_all()
|
||||
|
||||
with db.Session() as session:
|
||||
track1_path = "testdata/isa.mp3"
|
||||
metadata1 = helpers.get_file_metadata(track1_path)
|
||||
self.track1 = Tracks(session, **metadata1)
|
||||
# Test repr
|
||||
_ = str(self.track1)
|
||||
|
||||
track2_path = "testdata/mom.mp3"
|
||||
metadata2 = helpers.get_file_metadata(track2_path)
|
||||
self.track2 = Tracks(session, **metadata2)
|
||||
|
||||
def tearDown(self):
|
||||
db.drop_all()
|
||||
|
||||
def test_notecolours_get_colour(self):
|
||||
"""Create a colour record and retrieve all colours"""
|
||||
|
||||
note_colour = "#0bcdef"
|
||||
with db.Session() as session:
|
||||
NoteColours(session, substring="substring", colour=note_colour)
|
||||
|
||||
records = NoteColours.get_all(session)
|
||||
assert len(records) == 1
|
||||
record = records[0]
|
||||
assert record.colour == note_colour
|
||||
|
||||
def test_notecolours_get_all(self):
|
||||
"""Create two colour records and retrieve them all"""
|
||||
|
||||
note1_colour = "#1bcdef"
|
||||
note2_colour = "#20ff00"
|
||||
with db.Session() as session:
|
||||
NoteColours(session, substring="note1", colour=note1_colour)
|
||||
NoteColours(session, substring="note2", colour=note2_colour)
|
||||
|
||||
records = NoteColours.get_all(session)
|
||||
assert len(records) == 2
|
||||
assert note1_colour in [n.colour for n in records]
|
||||
assert note2_colour in [n.colour for n in records]
|
||||
|
||||
def test_notecolours_get_colour_none(self):
|
||||
note_colour = "#3bcdef"
|
||||
with db.Session() as session:
|
||||
NoteColours(session, substring="substring", colour=note_colour)
|
||||
|
||||
result = NoteColours.get_colour(session, "xyz")
|
||||
assert result is None
|
||||
|
||||
def test_notecolours_get_colour_match(self):
|
||||
note_colour = "#4bcdef"
|
||||
with db.Session() as session:
|
||||
nc = NoteColours(session, substring="sub", colour=note_colour)
|
||||
assert nc
|
||||
|
||||
result = NoteColours.get_colour(session, "The substring")
|
||||
assert result == note_colour
|
||||
|
||||
def test_playdates_add_playdate(self):
|
||||
"""Test playdate and last_played retrieval"""
|
||||
|
||||
with db.Session() as session:
|
||||
session.add(self.track1)
|
||||
playdate = Playdates(session, self.track1.id)
|
||||
assert playdate
|
||||
# test repr
|
||||
_ = str(playdate)
|
||||
|
||||
last_played = Playdates.last_played(session, self.track1.id)
|
||||
assert abs((playdate.lastplayed - last_played).total_seconds()) < 2
|
||||
|
||||
def test_playdates_played_after(self):
|
||||
with db.Session() as session:
|
||||
session.add(self.track1)
|
||||
playdate = Playdates(session, self.track1.id)
|
||||
yesterday = dt.datetime.now() - dt.timedelta(days=1)
|
||||
played = Playdates.played_after(session, yesterday)
|
||||
|
||||
assert len(played) == 1
|
||||
assert played[0] == playdate
|
||||
|
||||
def test_playlist_create(self):
|
||||
TEMPLATE_NAME = "my template"
|
||||
|
||||
with db.Session() as session:
|
||||
playlist = Playlists(session, "my playlist")
|
||||
assert playlist
|
||||
# test repr
|
||||
_ = str(playlist)
|
||||
|
||||
# test clear tabs
|
||||
Playlists.clear_tabs(session, [playlist.id])
|
||||
|
||||
# create template
|
||||
Playlists.save_as_template(session, playlist.id, TEMPLATE_NAME)
|
||||
|
||||
# test create template
|
||||
_ = Playlists.create_playlist_from_template(
|
||||
session, playlist, "my new name"
|
||||
)
|
||||
|
||||
# get all templates
|
||||
all_templates = Playlists.get_all_templates(session)
|
||||
assert len(all_templates) == 1
|
||||
# Save as template creates new playlist
|
||||
assert all_templates[0] != playlist
|
||||
# test delete playlist
|
||||
playlist.delete(session)
|
||||
|
||||
def test_playlist_open_and_close(self):
|
||||
# We need a playlist
|
||||
with db.Session() as session:
|
||||
playlist = Playlists(session, "my playlist")
|
||||
|
||||
assert len(Playlists.get_open(session)) == 0
|
||||
assert len(Playlists.get_closed(session)) == 1
|
||||
|
||||
playlist.mark_open()
|
||||
|
||||
assert len(Playlists.get_open(session)) == 1
|
||||
assert len(Playlists.get_closed(session)) == 0
|
||||
|
||||
playlist.close()
|
||||
|
||||
assert len(Playlists.get_open(session)) == 0
|
||||
assert len(Playlists.get_closed(session)) == 1
|
||||
|
||||
def test_playlist_get_all_and_by_id(self):
|
||||
# We need two playlists
|
||||
p1_name = "playlist one"
|
||||
p2_name = "playlist two"
|
||||
with db.Session() as session:
|
||||
playlist1 = Playlists(session, p1_name)
|
||||
_ = Playlists(session, p2_name)
|
||||
|
||||
all_playlists = Playlists.get_all(session)
|
||||
assert len(all_playlists) == 2
|
||||
assert p1_name in [p.name for p in all_playlists]
|
||||
assert p2_name in [p.name for p in all_playlists]
|
||||
assert session.get(Playlists, playlist1.id).name == p1_name
|
||||
|
||||
def test_tracks_get_all_tracks(self):
|
||||
# Need two tracks
|
||||
|
||||
with db.Session() as session:
|
||||
session.add(self.track1)
|
||||
session.add(self.track2)
|
||||
result = [a.path for a in Tracks.get_all(session)]
|
||||
assert self.track1.path in result
|
||||
assert self.track2.path in result
|
||||
|
||||
def test_tracks_by_path(self):
|
||||
with db.Session() as session:
|
||||
session.add(self.track1)
|
||||
assert Tracks.get_by_path(session, self.track1.path) is self.track1
|
||||
|
||||
def test_tracks_by_id(self):
|
||||
with db.Session() as session:
|
||||
session.add(self.track1)
|
||||
assert session.get(Tracks, self.track1.id) is self.track1
|
||||
|
||||
def test_tracks_search_artists(self):
|
||||
track1_artist = "Fleetwood Mac"
|
||||
|
||||
with db.Session() as session:
|
||||
session.add(self.track1)
|
||||
assert len(Tracks.search_artists(session, track1_artist)) == 1
|
||||
|
||||
def test_tracks_search_titles(self):
|
||||
track1_title = "I'm So Afraid"
|
||||
|
||||
with db.Session() as session:
|
||||
session.add(self.track1)
|
||||
assert len(Tracks.search_titles(session, track1_title)) == 1
|
||||
|
||||
def test_repr(self):
|
||||
"""Just check for error retrieving reprs"""
|
||||
|
||||
with db.Session() as session:
|
||||
nc = NoteColours(session, substring="x", colour="x")
|
||||
_ = str(nc)
|
||||
|
||||
def test_get_colour(self):
|
||||
"""Test for errors in execution"""
|
||||
|
||||
GOOD_STRING = "cantelope"
|
||||
BAD_STRING = "ericTheBee"
|
||||
SUBSTR = "ant"
|
||||
COLOUR = "blue"
|
||||
|
||||
with db.Session() as session:
|
||||
nc1 = NoteColours(
|
||||
session, substring=SUBSTR, colour=COLOUR, is_casesensitive=True
|
||||
)
|
||||
|
||||
_ = nc1.get_colour(session, "")
|
||||
colour = nc1.get_colour(session, GOOD_STRING)
|
||||
assert colour == COLOUR
|
||||
|
||||
colour = nc1.get_colour(session, BAD_STRING)
|
||||
assert colour is None
|
||||
|
||||
nc2 = NoteColours(
|
||||
session, substring=".*" + SUBSTR, colour=COLOUR, is_regex=True
|
||||
)
|
||||
colour = nc2.get_colour(session, GOOD_STRING)
|
||||
assert colour == COLOUR
|
||||
|
||||
print(">>>text_notcolours_get_colour")
|
||||
note_colour = "#0bcdef"
|
||||
NoteColours(session, substring="substring", colour=note_colour)
|
||||
colour = nc2.get_colour(session, BAD_STRING)
|
||||
assert colour is None
|
||||
|
||||
records = NoteColours.get_all(session)
|
||||
assert len(records) == 1
|
||||
record = records[0]
|
||||
assert record.colour == note_colour
|
||||
nc3 = NoteColours(
|
||||
session,
|
||||
substring=".*" + SUBSTR,
|
||||
colour=COLOUR,
|
||||
is_regex=True,
|
||||
is_casesensitive=True,
|
||||
)
|
||||
|
||||
colour = nc3.get_colour(session, GOOD_STRING)
|
||||
assert colour == COLOUR
|
||||
|
||||
colour = nc3.get_colour(session, BAD_STRING)
|
||||
assert colour is None
|
||||
|
||||
def test_create_cart(self):
|
||||
with db.Session() as session:
|
||||
cart = Carts(session, 1, "name")
|
||||
assert cart
|
||||
_ = str(cart)
|
||||
|
||||
def test_name_available(self):
|
||||
PLAYLIST_NAME = "a name"
|
||||
RENAME = "new name"
|
||||
|
||||
def test_notecolours_get_all(session):
|
||||
"""Create two colour records and retrieve them all"""
|
||||
with db.Session() as session:
|
||||
if Playlists.name_is_available(session, PLAYLIST_NAME):
|
||||
playlist = Playlists(session, PLAYLIST_NAME)
|
||||
assert playlist
|
||||
|
||||
print(">>>text_notcolours_get_all")
|
||||
note1_colour = "#1bcdef"
|
||||
note2_colour = "#20ff00"
|
||||
NoteColours(session, substring="note1", colour=note1_colour)
|
||||
NoteColours(session, substring="note2", colour=note2_colour)
|
||||
assert Playlists.name_is_available(session, PLAYLIST_NAME) is False
|
||||
|
||||
playlist.rename(session, RENAME)
|
||||
|
||||
records = NoteColours.get_all(session)
|
||||
assert len(records) == 2
|
||||
assert note1_colour in [n.colour for n in records]
|
||||
assert note2_colour in [n.colour for n in records]
|
||||
def test_create_playlist_row(self):
|
||||
PLAYLIST_NAME = "a name"
|
||||
|
||||
with db.Session() as session:
|
||||
if Playlists.name_is_available(session, PLAYLIST_NAME):
|
||||
playlist = Playlists(session, PLAYLIST_NAME)
|
||||
|
||||
def test_notecolours_get_colour_none(session):
|
||||
note_colour = "#3bcdef"
|
||||
NoteColours(session, substring="substring", colour=note_colour)
|
||||
plr = PlaylistRows(session, playlist.id, 1)
|
||||
assert plr
|
||||
_ = str(plr)
|
||||
plr.append_note("a note")
|
||||
plr.append_note("another note")
|
||||
|
||||
result = NoteColours.get_colour(session, "xyz")
|
||||
assert result is None
|
||||
def test_delete_plr(self):
|
||||
PLAYLIST_NAME = "a name"
|
||||
|
||||
with db.Session() as session:
|
||||
if Playlists.name_is_available(session, PLAYLIST_NAME):
|
||||
playlist = Playlists(session, PLAYLIST_NAME)
|
||||
|
||||
def test_notecolours_get_colour_match(session):
|
||||
note_colour = "#4bcdef"
|
||||
nc = NoteColours(session, substring="sub", colour=note_colour)
|
||||
assert nc
|
||||
|
||||
result = NoteColours.get_colour(session, "The substring")
|
||||
assert result == note_colour
|
||||
|
||||
|
||||
def test_playdates_add_playdate(session, track1):
|
||||
"""Test playdate and last_played retrieval"""
|
||||
|
||||
playdate = Playdates(session, track1.id)
|
||||
assert playdate
|
||||
print(playdate)
|
||||
|
||||
last_played = Playdates.last_played(session, track1.id)
|
||||
assert abs((playdate.lastplayed - last_played).total_seconds()) < 2
|
||||
|
||||
|
||||
def test_playdates_played_after(session, track1):
|
||||
playdate = Playdates(session, track1.id)
|
||||
yesterday = dt.datetime.now() - dt.timedelta(days=1)
|
||||
played = Playdates.played_after(session, yesterday)
|
||||
|
||||
assert len(played) == 1
|
||||
assert played[0] == playdate
|
||||
|
||||
|
||||
def test_playlist_create(session):
|
||||
TEMPLATE_NAME = "my template"
|
||||
|
||||
playlist = Playlists(session, "my playlist")
|
||||
assert playlist
|
||||
print(playlist)
|
||||
|
||||
# test clear tabs
|
||||
Playlists.clear_tabs(session, [playlist.id])
|
||||
|
||||
# create template
|
||||
Playlists.save_as_template(session, playlist.id, TEMPLATE_NAME)
|
||||
|
||||
# test create template
|
||||
_ = Playlists.create_playlist_from_template(session, playlist, "my new name")
|
||||
|
||||
# get all templates
|
||||
all_templates = Playlists.get_all_templates(session)
|
||||
assert len(all_templates) == 1
|
||||
# Save as template creates new playlist
|
||||
assert all_templates[0] != playlist
|
||||
# test delete playlist
|
||||
playlist.delete(session)
|
||||
|
||||
|
||||
def test_playlist_open_and_close(session):
|
||||
# We need a playlist
|
||||
playlist = Playlists(session, "my playlist")
|
||||
|
||||
assert len(Playlists.get_open(session)) == 0
|
||||
assert len(Playlists.get_closed(session)) == 1
|
||||
|
||||
playlist.mark_open()
|
||||
|
||||
assert len(Playlists.get_open(session)) == 1
|
||||
assert len(Playlists.get_closed(session)) == 0
|
||||
|
||||
playlist.close()
|
||||
|
||||
assert len(Playlists.get_open(session)) == 0
|
||||
assert len(Playlists.get_closed(session)) == 1
|
||||
|
||||
|
||||
def test_playlist_get_all_and_by_id(session):
|
||||
# We need two playlists
|
||||
p1_name = "playlist one"
|
||||
p2_name = "playlist two"
|
||||
playlist1 = Playlists(session, p1_name)
|
||||
_ = Playlists(session, p2_name)
|
||||
|
||||
all_playlists = Playlists.get_all(session)
|
||||
assert len(all_playlists) == 2
|
||||
assert p1_name in [p.name for p in all_playlists]
|
||||
assert p2_name in [p.name for p in all_playlists]
|
||||
assert session.get(Playlists, playlist1.id).name == p1_name
|
||||
|
||||
|
||||
def test_tracks_get_all_tracks(session, track1, track2):
|
||||
# Need two tracks
|
||||
|
||||
result = [a.path for a in Tracks.get_all(session)]
|
||||
assert track1.path in result
|
||||
assert track2.path in result
|
||||
|
||||
|
||||
def test_tracks_by_path(session, track1):
|
||||
|
||||
assert Tracks.get_by_path(session, track1.path) is track1
|
||||
|
||||
|
||||
def test_tracks_by_id(session, track1):
|
||||
|
||||
assert session.get(Tracks, track1.id) is track1
|
||||
|
||||
|
||||
def test_tracks_search_artists(session, track1):
|
||||
track1_artist = "Fleetwood Mac"
|
||||
|
||||
assert len(Tracks.search_artists(session, track1_artist)) == 1
|
||||
|
||||
|
||||
def test_tracks_search_titles(session, track1):
|
||||
track1_title = "I'm So Afraid"
|
||||
|
||||
assert len(Tracks.search_titles(session, track1_title)) == 1
|
||||
|
||||
def test_repr(session):
|
||||
"""Just check for error retrieving reprs"""
|
||||
|
||||
nc = NoteColours(session, substring="x", colour="x")
|
||||
print(nc)
|
||||
|
||||
|
||||
def test_get_colour(session):
|
||||
"""Test for errors in execution"""
|
||||
|
||||
GOOD_STRING = "cantelope"
|
||||
BAD_STRING = "ericTheBee"
|
||||
SUBSTR = "ant"
|
||||
COLOUR = "blue"
|
||||
|
||||
nc1 = NoteColours(session, substring=SUBSTR, colour=COLOUR, is_casesensitive=True)
|
||||
|
||||
_ = nc1.get_colour(session, "")
|
||||
colour = nc1.get_colour(session, GOOD_STRING)
|
||||
assert colour == COLOUR
|
||||
|
||||
colour = nc1.get_colour(session, BAD_STRING)
|
||||
assert colour is None
|
||||
|
||||
nc2 = NoteColours(session, substring=".*" + SUBSTR, colour=COLOUR, is_regex=True)
|
||||
colour = nc2.get_colour(session, GOOD_STRING)
|
||||
assert colour == COLOUR
|
||||
|
||||
colour = nc2.get_colour(session, BAD_STRING)
|
||||
assert colour is None
|
||||
|
||||
nc3 = NoteColours(
|
||||
session, substring=".*" + SUBSTR, colour=COLOUR, is_regex=True, is_casesensitive=True
|
||||
)
|
||||
|
||||
colour = nc3.get_colour(session, GOOD_STRING)
|
||||
assert colour == COLOUR
|
||||
|
||||
colour = nc3.get_colour(session, BAD_STRING)
|
||||
assert colour is None
|
||||
|
||||
|
||||
def test_create_cart(session):
|
||||
cart = Carts(session, 1, "name")
|
||||
assert cart
|
||||
print(cart)
|
||||
|
||||
|
||||
def test_name_available(session):
|
||||
PLAYLIST_NAME = "a name"
|
||||
RENAME = "new name"
|
||||
|
||||
if Playlists.name_is_available(session, PLAYLIST_NAME):
|
||||
playlist = Playlists(session, PLAYLIST_NAME)
|
||||
assert playlist
|
||||
|
||||
assert Playlists.name_is_available(session, PLAYLIST_NAME) is False
|
||||
|
||||
playlist.rename(session, RENAME)
|
||||
|
||||
|
||||
def test_create_playlist_row(session):
|
||||
PLAYLIST_NAME = "a name"
|
||||
|
||||
if Playlists.name_is_available(session, PLAYLIST_NAME):
|
||||
playlist = Playlists(session, PLAYLIST_NAME)
|
||||
|
||||
plr = PlaylistRows(session, playlist.id, 1)
|
||||
assert plr
|
||||
print(plr)
|
||||
plr.append_note("a note")
|
||||
plr.append_note("another note")
|
||||
|
||||
|
||||
def test_delete_plr(session):
|
||||
PLAYLIST_NAME = "a name"
|
||||
|
||||
if Playlists.name_is_available(session, PLAYLIST_NAME):
|
||||
playlist = Playlists(session, PLAYLIST_NAME)
|
||||
|
||||
plr = PlaylistRows(session, playlist.id, 1)
|
||||
assert plr
|
||||
PlaylistRows.delete_higher_rows(session, plr.playlist_id, 10)
|
||||
|
||||
assert PlaylistRows.get_track_plr(session, 12, plr.playlist_id) is None
|
||||
|
||||
plr = PlaylistRows(session, playlist.id, 1)
|
||||
assert plr
|
||||
PlaylistRows.delete_higher_rows(session, plr.playlist_id, 10)
|
||||
|
||||
assert PlaylistRows.get_track_plr(session, 12, plr.playlist_id) is None
|
||||
|
||||
@ -1,380 +1,223 @@
|
||||
# Standard library imports
|
||||
import os
|
||||
import unittest
|
||||
from typing import Optional
|
||||
|
||||
from app.models import (
|
||||
Playlists,
|
||||
Tracks,
|
||||
)
|
||||
# PyQt imports
|
||||
from PyQt6.QtCore import Qt, QModelIndex
|
||||
|
||||
# Third party imports
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
# App imports
|
||||
from app.log import log
|
||||
from app.helpers import get_file_metadata
|
||||
from app import playlistmodel
|
||||
from dbconfig import scoped_session
|
||||
|
||||
test_tracks = [
|
||||
"testdata/isa.mp3",
|
||||
"testdata/isa_with_gap.mp3",
|
||||
"testdata/loser.mp3",
|
||||
"testdata/lovecats-10seconds.mp3",
|
||||
"testdata/lovecats.mp3",
|
||||
"testdata/mom.mp3",
|
||||
"testdata/sitting.mp3",
|
||||
]
|
||||
|
||||
|
||||
def create_model_with_tracks(session: scoped_session, name: Optional[str] = None) -> "playlistmodel.PlaylistModel":
|
||||
playlist = Playlists(session, name or "test playlist")
|
||||
model = playlistmodel.PlaylistModel(playlist.id)
|
||||
|
||||
for row in range(len(test_tracks)):
|
||||
track_path = test_tracks[row % len(test_tracks)]
|
||||
metadata = get_file_metadata(track_path)
|
||||
track = Tracks(session, **metadata)
|
||||
model.insert_row(proposed_row_number=row, track_id=track.id, note=f"{row=}")
|
||||
|
||||
session.commit()
|
||||
return model
|
||||
|
||||
|
||||
def create_model_with_playlist_rows(
|
||||
session: scoped_session, rows: int, name: Optional[str] = None
|
||||
) -> "playlistmodel.PlaylistModel":
|
||||
playlist = Playlists(session, name or "test playlist")
|
||||
# Create a model
|
||||
model = playlistmodel.PlaylistModel(playlist.id)
|
||||
for row in range(rows):
|
||||
model.insert_row(proposed_row_number=row, note=str(row))
|
||||
|
||||
session.commit()
|
||||
return model
|
||||
|
||||
|
||||
def test_11_row_playlist(monkeypatch, session):
|
||||
# Create multirow playlist
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
model = create_model_with_playlist_rows(session, 11)
|
||||
assert model.rowCount() == 11
|
||||
assert max(model.playlist_rows.keys()) == 10
|
||||
for row in range(model.rowCount()):
|
||||
assert row in model.playlist_rows
|
||||
assert model.playlist_rows[row].plr_rownum == row
|
||||
|
||||
|
||||
def test_move_rows_test2(monkeypatch, session):
|
||||
# move row 3 to row 5
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
model = create_model_with_playlist_rows(session, 11)
|
||||
model.move_rows([3], 5)
|
||||
# Check we have all rows and plr_rownums are correct
|
||||
for row in range(model.rowCount()):
|
||||
assert row in model.playlist_rows
|
||||
assert model.playlist_rows[row].plr_rownum == row
|
||||
if row not in [3, 4, 5]:
|
||||
assert model.playlist_rows[row].note == str(row)
|
||||
elif row == 3:
|
||||
assert model.playlist_rows[row].note == str(4)
|
||||
elif row == 4:
|
||||
assert model.playlist_rows[row].note == str(5)
|
||||
elif row == 5:
|
||||
assert model.playlist_rows[row].note == str(3)
|
||||
|
||||
|
||||
def test_move_rows_test3(monkeypatch, session):
|
||||
# move row 4 to row 3
|
||||
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
model = create_model_with_playlist_rows(session, 11)
|
||||
model.move_rows([4], 3)
|
||||
|
||||
# Check we have all rows and plr_rownums are correct
|
||||
for row in range(model.rowCount()):
|
||||
assert row in model.playlist_rows
|
||||
assert model.playlist_rows[row].plr_rownum == row
|
||||
if row not in [3, 4]:
|
||||
assert model.playlist_rows[row].note == str(row)
|
||||
elif row == 3:
|
||||
assert model.playlist_rows[row].note == str(4)
|
||||
elif row == 4:
|
||||
assert model.playlist_rows[row].note == str(3)
|
||||
|
||||
|
||||
def test_move_rows_test4(monkeypatch, session):
|
||||
# move row 4 to row 2
|
||||
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
model = create_model_with_playlist_rows(session, 11)
|
||||
model.move_rows([4], 2)
|
||||
|
||||
# Check we have all rows and plr_rownums are correct
|
||||
for row in range(model.rowCount()):
|
||||
assert row in model.playlist_rows
|
||||
assert model.playlist_rows[row].plr_rownum == row
|
||||
if row not in [2, 3, 4]:
|
||||
assert model.playlist_rows[row].note == str(row)
|
||||
elif row == 2:
|
||||
assert model.playlist_rows[row].note == str(4)
|
||||
elif row == 3:
|
||||
assert model.playlist_rows[row].note == str(2)
|
||||
elif row == 4:
|
||||
assert model.playlist_rows[row].note == str(3)
|
||||
|
||||
|
||||
def test_move_rows_test5(monkeypatch, session):
|
||||
# move rows [1, 4, 5, 10] → 8
|
||||
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
model = create_model_with_playlist_rows(session, 11)
|
||||
model.move_rows([1, 4, 5, 10], 8)
|
||||
|
||||
# Check we have all rows and plr_rownums are correct
|
||||
new_order = []
|
||||
for row in range(model.rowCount()):
|
||||
assert row in model.playlist_rows
|
||||
assert model.playlist_rows[row].plr_rownum == row
|
||||
new_order.append(int(model.playlist_rows[row].note))
|
||||
assert new_order == [0, 2, 3, 6, 7, 8, 9, 1, 4, 5, 10]
|
||||
|
||||
|
||||
def test_move_rows_test6(monkeypatch, session):
|
||||
# move rows [3, 6] → 5
|
||||
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
model = create_model_with_playlist_rows(session, 11)
|
||||
model.move_rows([3, 6], 5)
|
||||
|
||||
# Check we have all rows and plr_rownums are correct
|
||||
new_order = []
|
||||
for row in range(model.rowCount()):
|
||||
assert row in model.playlist_rows
|
||||
assert model.playlist_rows[row].plr_rownum == row
|
||||
new_order.append(int(model.playlist_rows[row].note))
|
||||
assert new_order == [0, 1, 2, 4, 5, 3, 6, 7, 8, 9, 10]
|
||||
|
||||
# Set up test database before importing db
|
||||
# Mark subsequent lines to ignore E402, imports not at top of file
|
||||
DB_FILE = '/tmp/mm.db'
|
||||
if os.path.exists(DB_FILE):
|
||||
os.unlink(DB_FILE)
|
||||
os.environ['ALCHEMICAL_DATABASE_URI'] = 'sqlite:///' + DB_FILE
|
||||
from app import playlistmodel # noqa: E402
|
||||
from app.models import ( # noqa: E402
|
||||
db,
|
||||
Playlists,
|
||||
Settings,
|
||||
Tracks,
|
||||
)
|
||||
|
||||
|
||||
class TestMMMisc(unittest.TestCase):
|
||||
def setUp(self):
|
||||
PLAYLIST_NAME = "test playlist"
|
||||
self.test_tracks = [
|
||||
"testdata/isa.mp3",
|
||||
"testdata/isa_with_gap.mp3",
|
||||
"testdata/loser.mp3",
|
||||
"testdata/lovecats-10seconds.mp3",
|
||||
"testdata/lovecats.mp3",
|
||||
"testdata/mom.mp3",
|
||||
"testdata/sitting.mp3",
|
||||
]
|
||||
|
||||
db.create_all()
|
||||
|
||||
# Create a playlist and model
|
||||
with db.Session() as session:
|
||||
self.playlist = Playlists(session, PLAYLIST_NAME)
|
||||
self.model = playlistmodel.PlaylistModel(self.playlist.id)
|
||||
|
||||
for row in range(len(self.test_tracks)):
|
||||
track_path = self.test_tracks[row % len(self.test_tracks)]
|
||||
metadata = get_file_metadata(track_path)
|
||||
track = Tracks(session, **metadata)
|
||||
self.model.insert_row(proposed_row_number=row, track_id=track.id, note=f"{row=}")
|
||||
|
||||
session.commit()
|
||||
|
||||
def tearDown(self):
|
||||
db.drop_all()
|
||||
|
||||
def test_7_row_playlist(self):
|
||||
# Test auto-created playlist
|
||||
|
||||
assert self.model.rowCount() == 7
|
||||
assert max(self.model.playlist_rows.keys()) == 6
|
||||
for row in range(self.model.rowCount()):
|
||||
assert row in self.model.playlist_rows
|
||||
assert self.model.playlist_rows[row].plr_rownum == row
|
||||
|
||||
|
||||
# def test_move_rows_test2(monkeypatch, session):
|
||||
# # move row 3 to row 5
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
# model = create_model_with_playlist_rows(session, 11)
|
||||
# model.move_rows([3], 5)
|
||||
# # Check we have all rows and plr_rownums are correct
|
||||
# for row in range(model.rowCount()):
|
||||
# assert row in model.playlist_rows
|
||||
# assert model.playlist_rows[row].plr_rownum == row
|
||||
# if row not in [3, 4, 5]:
|
||||
# assert model.playlist_rows[row].note == str(row)
|
||||
# elif row == 3:
|
||||
# assert model.playlist_rows[row].note == str(4)
|
||||
# elif row == 4:
|
||||
# assert model.playlist_rows[row].note == str(5)
|
||||
# elif row == 5:
|
||||
# assert model.playlist_rows[row].note == str(3)
|
||||
|
||||
|
||||
# def test_move_rows_test3(monkeypatch, session):
|
||||
# # move row 4 to row 3
|
||||
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
# model = create_model_with_playlist_rows(session, 11)
|
||||
# model.move_rows([4], 3)
|
||||
|
||||
# # Check we have all rows and plr_rownums are correct
|
||||
# for row in range(model.rowCount()):
|
||||
# assert row in model.playlist_rows
|
||||
# assert model.playlist_rows[row].plr_rownum == row
|
||||
# if row not in [3, 4]:
|
||||
# assert model.playlist_rows[row].note == str(row)
|
||||
# elif row == 3:
|
||||
# assert model.playlist_rows[row].note == str(4)
|
||||
# elif row == 4:
|
||||
# assert model.playlist_rows[row].note == str(3)
|
||||
|
||||
|
||||
# def test_move_rows_test4(monkeypatch, session):
|
||||
# # move row 4 to row 2
|
||||
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
# model = create_model_with_playlist_rows(session, 11)
|
||||
# model.move_rows([4], 2)
|
||||
|
||||
# # Check we have all rows and plr_rownums are correct
|
||||
# for row in range(model.rowCount()):
|
||||
# assert row in model.playlist_rows
|
||||
# assert model.playlist_rows[row].plr_rownum == row
|
||||
# if row not in [2, 3, 4]:
|
||||
# assert model.playlist_rows[row].note == str(row)
|
||||
# elif row == 2:
|
||||
# assert model.playlist_rows[row].note == str(4)
|
||||
# elif row == 3:
|
||||
# assert model.playlist_rows[row].note == str(2)
|
||||
# elif row == 4:
|
||||
# assert model.playlist_rows[row].note == str(3)
|
||||
|
||||
|
||||
# def test_move_rows_test5(monkeypatch, session):
|
||||
# # move rows [1, 4, 5, 10] → 8
|
||||
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
# model = create_model_with_playlist_rows(session, 11)
|
||||
# model.move_rows([1, 4, 5, 10], 8)
|
||||
|
||||
def test_move_rows_test7(monkeypatch, session):
|
||||
# move rows [3, 5, 6] → 8
|
||||
# # Check we have all rows and plr_rownums are correct
|
||||
# new_order = []
|
||||
# for row in range(model.rowCount()):
|
||||
# assert row in model.playlist_rows
|
||||
# assert model.playlist_rows[row].plr_rownum == row
|
||||
# new_order.append(int(model.playlist_rows[row].note))
|
||||
# assert new_order == [0, 2, 3, 6, 7, 8, 9, 1, 4, 5, 10]
|
||||
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
model = create_model_with_playlist_rows(session, 11)
|
||||
model.move_rows([3, 5, 6], 8)
|
||||
# def test_move_rows_test6(monkeypatch, session):
|
||||
# # move rows [3, 6] → 5
|
||||
|
||||
# Check we have all rows and plr_rownums are correct
|
||||
new_order = []
|
||||
for row in range(model.rowCount()):
|
||||
assert row in model.playlist_rows
|
||||
assert model.playlist_rows[row].plr_rownum == row
|
||||
new_order.append(int(model.playlist_rows[row].note))
|
||||
assert new_order == [0, 1, 2, 4, 7, 8, 9, 10, 3, 5, 6]
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
# model = create_model_with_playlist_rows(session, 11)
|
||||
# model.move_rows([3, 6], 5)
|
||||
|
||||
def test_move_rows_test8(monkeypatch, session):
|
||||
# move rows [7, 8, 10] → 5
|
||||
# # Check we have all rows and plr_rownums are correct
|
||||
# new_order = []
|
||||
# for row in range(model.rowCount()):
|
||||
# assert row in model.playlist_rows
|
||||
# assert model.playlist_rows[row].plr_rownum == row
|
||||
# new_order.append(int(model.playlist_rows[row].note))
|
||||
# assert new_order == [0, 1, 2, 4, 5, 3, 6, 7, 8, 9, 10]
|
||||
|
||||
|
||||
# def test_move_rows_test7(monkeypatch, session):
|
||||
# # move rows [3, 5, 6] → 8
|
||||
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
model = create_model_with_playlist_rows(session, 11)
|
||||
model.move_rows([7, 8, 10], 5)
|
||||
# model = create_model_with_playlist_rows(session, 11)
|
||||
# model.move_rows([3, 5, 6], 8)
|
||||
|
||||
# # Check we have all rows and plr_rownums are correct
|
||||
# new_order = []
|
||||
# for row in range(model.rowCount()):
|
||||
# assert row in model.playlist_rows
|
||||
# assert model.playlist_rows[row].plr_rownum == row
|
||||
# new_order.append(int(model.playlist_rows[row].note))
|
||||
# assert new_order == [0, 1, 2, 4, 7, 8, 9, 10, 3, 5, 6]
|
||||
|
||||
# Check we have all rows and plr_rownums are correct
|
||||
new_order = []
|
||||
for row in range(model.rowCount()):
|
||||
assert row in model.playlist_rows
|
||||
assert model.playlist_rows[row].plr_rownum == row
|
||||
new_order.append(int(model.playlist_rows[row].note))
|
||||
assert new_order == [0, 1, 2, 3, 4, 7, 8, 10, 5, 6, 9]
|
||||
|
||||
# def test_move_rows_test8(monkeypatch, session):
|
||||
# # move rows [7, 8, 10] → 5
|
||||
|
||||
def test_insert_header_row_end(monkeypatch, session):
|
||||
# insert header row at end of playlist
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
note_text = "test text"
|
||||
initial_row_count = 11
|
||||
# model = create_model_with_playlist_rows(session, 11)
|
||||
# model.move_rows([7, 8, 10], 5)
|
||||
|
||||
model = create_model_with_playlist_rows(session, initial_row_count)
|
||||
model.insert_row(proposed_row_number=None, note=note_text)
|
||||
assert model.rowCount() == initial_row_count + 1
|
||||
prd = model.playlist_rows[model.rowCount() - 1]
|
||||
# Test against edit_role because display_role for headers is
|
||||
# handled differently (sets up row span)
|
||||
assert (
|
||||
model.edit_role(model.rowCount() - 1, playlistmodel.Col.NOTE.value, prd)
|
||||
== note_text
|
||||
)
|
||||
# # Check we have all rows and plr_rownums are correct
|
||||
# new_order = []
|
||||
# for row in range(model.rowCount()):
|
||||
# assert row in model.playlist_rows
|
||||
# assert model.playlist_rows[row].plr_rownum == row
|
||||
# new_order.append(int(model.playlist_rows[row].note))
|
||||
# assert new_order == [0, 1, 2, 3, 4, 7, 8, 10, 5, 6, 9]
|
||||
|
||||
|
||||
def test_insert_header_row_middle(monkeypatch, session):
|
||||
# insert header row in middle of playlist
|
||||
# def test_insert_header_row_end(monkeypatch, session):
|
||||
# # insert header row at end of playlist
|
||||
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
note_text = "test text"
|
||||
initial_row_count = 11
|
||||
insert_row = 6
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
# note_text = "test text"
|
||||
# initial_row_count = 11
|
||||
|
||||
model = create_model_with_playlist_rows(session, initial_row_count)
|
||||
model.insert_row(proposed_row_number=insert_row, note=note_text)
|
||||
assert model.rowCount() == initial_row_count + 1
|
||||
prd = model.playlist_rows[insert_row]
|
||||
# Test against edit_role because display_role for headers is
|
||||
# handled differently (sets up row span)
|
||||
assert (
|
||||
model.edit_role(model.rowCount() - 1, playlistmodel.Col.NOTE.value, prd)
|
||||
== note_text
|
||||
)
|
||||
# model = create_model_with_playlist_rows(session, initial_row_count)
|
||||
# model.insert_row(proposed_row_number=None, note=note_text)
|
||||
# assert model.rowCount() == initial_row_count + 1
|
||||
# prd = model.playlist_rows[model.rowCount() - 1]
|
||||
# # Test against edit_role because display_role for headers is
|
||||
# # handled differently (sets up row span)
|
||||
# assert (
|
||||
# model.edit_role(model.rowCount() - 1, playlistmodel.Col.NOTE.value, prd)
|
||||
# == note_text
|
||||
# )
|
||||
|
||||
|
||||
def test_add_track_to_header(monkeypatch, session):
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
note_text = "test text"
|
||||
initial_row_count = 11
|
||||
insert_row = 6
|
||||
|
||||
model = create_model_with_playlist_rows(session, initial_row_count)
|
||||
model.insert_row(proposed_row_number=insert_row, note=note_text)
|
||||
assert model.rowCount() == initial_row_count + 1
|
||||
|
||||
prd = model.playlist_rows[1]
|
||||
model.add_track_to_header(insert_row, prd.track_id)
|
||||
|
||||
|
||||
def test_create_model_with_tracks(monkeypatch, session):
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
model = create_model_with_tracks(session)
|
||||
assert len(model.playlist_rows) == len(test_tracks)
|
||||
|
||||
|
||||
def test_timing_one_track(monkeypatch, session):
|
||||
START_ROW = 0
|
||||
END_ROW = 2
|
||||
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
model = create_model_with_tracks(session)
|
||||
|
||||
model.insert_row(proposed_row_number=START_ROW, note="start+")
|
||||
model.insert_row(proposed_row_number=END_ROW, note="-")
|
||||
|
||||
prd = model.playlist_rows[START_ROW]
|
||||
qv_value = model.display_role(START_ROW, playlistmodel.HEADER_NOTES_COLUMN, prd)
|
||||
assert qv_value.value() == "start [1 tracks, 4:23 unplayed]"
|
||||
|
||||
|
||||
def test_insert_track_new_playlist(monkeypatch, session):
|
||||
# insert a track into a new playlist
|
||||
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
playlist = Playlists(session, "test playlist")
|
||||
# Create a model
|
||||
model = playlistmodel.PlaylistModel(playlist.id)
|
||||
|
||||
track_path = test_tracks[0]
|
||||
metadata = get_file_metadata(track_path)
|
||||
track = Tracks(session, **metadata)
|
||||
model.insert_row(proposed_row_number=0, track_id=track.id)
|
||||
|
||||
prd = model.playlist_rows[model.rowCount() - 1]
|
||||
assert (
|
||||
model.edit_role(model.rowCount() - 1, playlistmodel.Col.TITLE.value, prd)
|
||||
== metadata["title"]
|
||||
)
|
||||
|
||||
|
||||
def test_reverse_row_groups_one_row(monkeypatch, session):
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
rows_to_move = [3]
|
||||
|
||||
model_src = create_model_with_playlist_rows(session, 5, name="source")
|
||||
result = model_src._reversed_contiguous_row_groups(rows_to_move)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0] == [3]
|
||||
|
||||
|
||||
def test_reverse_row_groups_multiple_row(monkeypatch, session):
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
rows_to_move = [2, 3, 4, 5, 7, 9, 10, 13, 17, 20, 21]
|
||||
|
||||
model_src = create_model_with_playlist_rows(session, 5, name="source")
|
||||
result = model_src._reversed_contiguous_row_groups(rows_to_move)
|
||||
|
||||
assert result == [[20, 21], [17], [13], [9, 10], [7], [2, 3, 4, 5]]
|
||||
|
||||
|
||||
def test_move_one_row_between_playlists_to_end(monkeypatch, session):
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
create_rowcount = 5
|
||||
from_rows = [3]
|
||||
to_row = create_rowcount
|
||||
|
||||
model_src = create_model_with_playlist_rows(session, create_rowcount, name="source")
|
||||
model_dst = create_model_with_playlist_rows(session, create_rowcount, name="destination")
|
||||
|
||||
model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
|
||||
model_dst.refresh_data(session)
|
||||
|
||||
assert len(model_src.playlist_rows) == create_rowcount - len(from_rows)
|
||||
assert len(model_dst.playlist_rows) == create_rowcount + len(from_rows)
|
||||
assert sorted([a.plr_rownum for a in model_src.playlist_rows.values()]) == list(
|
||||
range(len(model_src.playlist_rows))
|
||||
)
|
||||
|
||||
|
||||
def test_move_one_row_between_playlists_to_middle(monkeypatch, session):
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
create_rowcount = 5
|
||||
from_rows = [3]
|
||||
to_row = 2
|
||||
|
||||
model_src = create_model_with_playlist_rows(session, create_rowcount, name="source")
|
||||
model_dst = create_model_with_playlist_rows(session, create_rowcount, name="destination")
|
||||
|
||||
model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
|
||||
model_dst.refresh_data(session)
|
||||
|
||||
# Check the rows of the destination model
|
||||
row_notes = []
|
||||
for row_number in range(model_dst.rowCount()):
|
||||
index = model_dst.index(row_number, playlistmodel.Col.TITLE.value, QModelIndex())
|
||||
row_notes.append(model_dst.data(index, Qt.ItemDataRole.EditRole).value())
|
||||
|
||||
assert len(model_src.playlist_rows) == create_rowcount - len(from_rows)
|
||||
assert len(model_dst.playlist_rows) == create_rowcount + len(from_rows)
|
||||
assert [int(a) for a in row_notes] == [0, 1, 3, 2, 3, 4]
|
||||
|
||||
|
||||
def test_move_multiple_rows_between_playlists_to_end(monkeypatch, session):
|
||||
monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
create_rowcount = 5
|
||||
from_rows = [1, 3, 4]
|
||||
to_row = 2
|
||||
|
||||
model_src = create_model_with_playlist_rows(session, create_rowcount, name="source")
|
||||
model_dst = create_model_with_playlist_rows(session, create_rowcount, name="destination")
|
||||
|
||||
model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
|
||||
model_dst.refresh_data(session)
|
||||
|
||||
# Check the rows of the destination model
|
||||
row_notes = []
|
||||
for row_number in range(model_dst.rowCount()):
|
||||
index = model_dst.index(row_number, playlistmodel.Col.TITLE.value, QModelIndex())
|
||||
row_notes.append(model_dst.data(index, Qt.ItemDataRole.EditRole).value())
|
||||
|
||||
assert len(model_src.playlist_rows) == create_rowcount - len(from_rows)
|
||||
assert len(model_dst.playlist_rows) == create_rowcount + len(from_rows)
|
||||
assert [int(a) for a in row_notes] == [0, 1, 3, 4, 1, 2, 3, 4]
|
||||
|
||||
|
||||
# def test_edit_header(monkeypatch, session): # edit header row in middle of playlist
|
||||
# def test_insert_header_row_middle(monkeypatch, session):
|
||||
# # insert header row in middle of playlist
|
||||
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
# note_text = "test text"
|
||||
@ -382,12 +225,188 @@ def test_move_multiple_rows_between_playlists_to_end(monkeypatch, session):
|
||||
# insert_row = 6
|
||||
|
||||
# model = create_model_with_playlist_rows(session, initial_row_count)
|
||||
# model.insert_header_row(insert_row, note_text)
|
||||
# model.insert_row(proposed_row_number=insert_row, note=note_text)
|
||||
# assert model.rowCount() == initial_row_count + 1
|
||||
# prd = model.playlist_rows[insert_row]
|
||||
# # Test against edit_role because display_role for headers is
|
||||
# # handled differently (sets up row span)
|
||||
# assert (
|
||||
# model.edit_role(model.rowCount(), playlistmodel.Col.NOTE.value, prd)
|
||||
# model.edit_role(model.rowCount() - 1, playlistmodel.Col.NOTE.value, prd)
|
||||
# == note_text
|
||||
# )
|
||||
|
||||
|
||||
# def test_add_track_to_header(monkeypatch, session):
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
# note_text = "test text"
|
||||
# initial_row_count = 11
|
||||
# insert_row = 6
|
||||
|
||||
# model = create_model_with_playlist_rows(session, initial_row_count)
|
||||
# model.insert_row(proposed_row_number=insert_row, note=note_text)
|
||||
# assert model.rowCount() == initial_row_count + 1
|
||||
|
||||
# prd = model.playlist_rows[1]
|
||||
# model.add_track_to_header(insert_row, prd.track_id)
|
||||
|
||||
|
||||
# def test_create_model_with_tracks(monkeypatch, session):
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
# model = create_model_with_tracks(session)
|
||||
# assert len(model.playlist_rows) == len(self.test_tracks)
|
||||
|
||||
|
||||
# def test_timing_one_track(monkeypatch, session):
|
||||
# START_ROW = 0
|
||||
# END_ROW = 2
|
||||
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
# model = create_model_with_tracks(session)
|
||||
|
||||
# model.insert_row(proposed_row_number=START_ROW, note="start+")
|
||||
# model.insert_row(proposed_row_number=END_ROW, note="-")
|
||||
|
||||
# prd = model.playlist_rows[START_ROW]
|
||||
# qv_value = model.display_role(START_ROW, playlistmodel.HEADER_NOTES_COLUMN, prd)
|
||||
# assert qv_value.value() == "start [1 tracks, 4:23 unplayed]"
|
||||
|
||||
|
||||
# def test_insert_track_new_playlist(monkeypatch, session):
|
||||
# # insert a track into a new playlist
|
||||
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
# playlist = Playlists(session, "test playlist")
|
||||
# # Create a model
|
||||
# model = playlistmodel.PlaylistModel(playlist.id)
|
||||
|
||||
# track_path = self.test_tracks[0]
|
||||
# metadata = get_file_metadata(track_path)
|
||||
# track = Tracks(session, **metadata)
|
||||
# model.insert_row(proposed_row_number=0, track_id=track.id)
|
||||
|
||||
# prd = model.playlist_rows[model.rowCount() - 1]
|
||||
# assert (
|
||||
# model.edit_role(model.rowCount() - 1, playlistmodel.Col.TITLE.value, prd)
|
||||
# == metadata["title"]
|
||||
# )
|
||||
|
||||
|
||||
# def test_reverse_row_groups_one_row(monkeypatch, session):
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
# rows_to_move = [3]
|
||||
|
||||
# model_src = create_model_with_playlist_rows(session, 5, name="source")
|
||||
# result = model_src._reversed_contiguous_row_groups(rows_to_move)
|
||||
|
||||
# assert len(result) == 1
|
||||
# assert result[0] == [3]
|
||||
|
||||
|
||||
# def test_reverse_row_groups_multiple_row(monkeypatch, session):
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
# rows_to_move = [2, 3, 4, 5, 7, 9, 10, 13, 17, 20, 21]
|
||||
|
||||
# model_src = create_model_with_playlist_rows(session, 5, name="source")
|
||||
# result = model_src._reversed_contiguous_row_groups(rows_to_move)
|
||||
|
||||
# assert result == [[20, 21], [17], [13], [9, 10], [7], [2, 3, 4, 5]]
|
||||
|
||||
|
||||
# def test_move_one_row_between_playlists_to_end(monkeypatch, session):
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
# create_rowcount = 5
|
||||
# from_rows = [3]
|
||||
# to_row = create_rowcount
|
||||
|
||||
# model_src = create_model_with_playlist_rows(session, create_rowcount, name="source")
|
||||
# model_dst = create_model_with_playlist_rows(
|
||||
# session, create_rowcount, name="destination"
|
||||
# )
|
||||
|
||||
# model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
|
||||
# model_dst.refresh_data(session)
|
||||
|
||||
# assert len(model_src.playlist_rows) == create_rowcount - len(from_rows)
|
||||
# assert len(model_dst.playlist_rows) == create_rowcount + len(from_rows)
|
||||
# assert sorted([a.plr_rownum for a in model_src.playlist_rows.values()]) == list(
|
||||
# range(len(model_src.playlist_rows))
|
||||
# )
|
||||
|
||||
|
||||
# def test_move_one_row_between_playlists_to_middle(monkeypatch, session):
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
# create_rowcount = 5
|
||||
# from_rows = [3]
|
||||
# to_row = 2
|
||||
|
||||
# model_src = create_model_with_playlist_rows(session, create_rowcount, name="source")
|
||||
# model_dst = create_model_with_playlist_rows(
|
||||
# session, create_rowcount, name="destination"
|
||||
# )
|
||||
|
||||
# model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
|
||||
# model_dst.refresh_data(session)
|
||||
|
||||
# # Check the rows of the destination model
|
||||
# row_notes = []
|
||||
# for row_number in range(model_dst.rowCount()):
|
||||
# index = model_dst.index(
|
||||
# row_number, playlistmodel.Col.TITLE.value, QModelIndex()
|
||||
# )
|
||||
# row_notes.append(model_dst.data(index, Qt.ItemDataRole.EditRole).value())
|
||||
|
||||
# assert len(model_src.playlist_rows) == create_rowcount - len(from_rows)
|
||||
# assert len(model_dst.playlist_rows) == create_rowcount + len(from_rows)
|
||||
# assert [int(a) for a in row_notes] == [0, 1, 3, 2, 3, 4]
|
||||
|
||||
|
||||
# def test_move_multiple_rows_between_playlists_to_end(monkeypatch, session):
|
||||
# monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
|
||||
# create_rowcount = 5
|
||||
# from_rows = [1, 3, 4]
|
||||
# to_row = 2
|
||||
|
||||
# model_src = create_model_with_playlist_rows(session, create_rowcount, name="source")
|
||||
# model_dst = create_model_with_playlist_rows(
|
||||
# session, create_rowcount, name="destination"
|
||||
# )
|
||||
|
||||
# model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
|
||||
# model_dst.refresh_data(session)
|
||||
|
||||
# # Check the rows of the destination model
|
||||
# row_notes = []
|
||||
# for row_number in range(model_dst.rowCount()):
|
||||
# index = model_dst.index(
|
||||
# row_number, playlistmodel.Col.TITLE.value, QModelIndex()
|
||||
# )
|
||||
# row_notes.append(model_dst.data(index, Qt.ItemDataRole.EditRole).value())
|
||||
|
||||
# assert len(model_src.playlist_rows) == create_rowcount - len(from_rows)
|
||||
# assert len(model_dst.playlist_rows) == create_rowcount + len(from_rows)
|
||||
# assert [int(a) for a in row_notes] == [0, 1, 3, 4, 1, 2, 3, 4]
|
||||
|
||||
|
||||
# # def test_edit_header(monkeypatch, session): # edit header row in middle of playlist
|
||||
|
||||
# # monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
# # note_text = "test text"
|
||||
# # initial_row_count = 11
|
||||
# # insert_row = 6
|
||||
|
||||
# # model = create_model_with_playlist_rows(session, initial_row_count)
|
||||
# # model.insert_header_row(insert_row, note_text)
|
||||
# # assert model.rowCount() == initial_row_count + 1
|
||||
# # prd = model.playlist_rows[insert_row]
|
||||
# # # Test against edit_role because display_role for headers is
|
||||
# # # handled differently (sets up row span)
|
||||
# # assert (
|
||||
# # model.edit_role(model.rowCount(), playlistmodel.Col.NOTE.value, prd)
|
||||
# # == note_text
|
||||
# # )
|
||||
|
||||
Loading…
Reference in New Issue
Block a user