move to xxh3 hashing algorithm

+ port: search
This commit is contained in:
cwilvx
2024-07-03 11:12:06 +03:00
parent ff7343a7be
commit a5634f267f
16 changed files with 322 additions and 182 deletions
+2
View File
@@ -2,6 +2,7 @@
<!-- TODO: ELABORATE --> <!-- TODO: ELABORATE -->
- Auth - Auth
- New artists/albums Sort by: last played, no. of streams, total stream duration
## Improvements ## Improvements
- The context menu now doesn't take forever to open up - The context menu now doesn't take forever to open up
@@ -12,6 +13,7 @@
- -
## Development ## Development
- Rewritten the whole DB layer to move stores from memory to the database.
## THE BIG ONE API CHANGES ## THE BIG ONE API CHANGES
+9 -1
View File
@@ -47,4 +47,12 @@
- Remove duplicates on artist page (test with Hanson) - Remove duplicates on artist page (test with Hanson)
- Test foreign keys on delete - Test foreign keys on delete
- Map scrobble info on app start - Map scrobble info on app start
- Make home page recent items faster! - Make home page recent items faster!
- Normalize playlists table:
- New table to hold playlist entries
- Normalize similar artists:
- New table to hold similar artist entries
- Create 2 way relationships, such that if an artist A is similar to another B with a certain weight,
then artist B is similar to A with the same weight, unless overwritten.
- Figure out how to update album/artist tables instead of deleting all rows when the app starts
- Move get all filtering and sorting operations to the database since all sort keys are table columns
-18
View File
@@ -166,12 +166,7 @@ def add_item_to_playlist(path: PlaylistIDPath, body: AddItemToPlaylistBody):
else: else:
trackhashes = [] trackhashes = []
# insert_count = PL.add_tracks_to_playlist(int(playlist_id), trackhashes)
PlaylistTable.append_to_playlist(int(playlist_id), trackhashes) PlaylistTable.append_to_playlist(int(playlist_id), trackhashes)
# if insert_count == 0:
# return {"error": "Item already exists in playlist"}, 409
return {"msg": "Done"}, 200 return {"msg": "Done"}, 200
@@ -211,15 +206,12 @@ def get_playlist(path: PlaylistIDPath, query: GetPlaylistQuery):
playlist, tracks = handler() playlist, tracks = handler()
return format_custom_playlist(playlist, tracks) return format_custom_playlist(playlist, tracks)
# playlist = PL.get_playlist_by_id(int(playlistid))
playlist = PlaylistTable.get_by_id(playlistid) playlist = PlaylistTable.get_by_id(playlistid)
if playlist is None: if playlist is None:
return {"msg": "Playlist not found"}, 404 return {"msg": "Playlist not found"}, 404
# tracks = TrackStore.get_tracks_by_trackhashes(list(playlist.trackhashes))
tracks = TrackTable.get_tracks_by_trackhashes(playlist.trackhashes) tracks = TrackTable.get_tracks_by_trackhashes(playlist.trackhashes)
tracks = remove_duplicates(tracks)
duration = sum(t.duration for t in tracks) duration = sum(t.duration for t in tracks)
playlist.last_updated = date_string_to_time_passed(playlist.last_updated) playlist.last_updated = date_string_to_time_passed(playlist.last_updated)
@@ -250,7 +242,6 @@ def update_playlist_info(path: PlaylistIDPath, form: UpdatePlaylistForm):
Update playlist Update playlist
""" """
playlistid = path.playlistid playlistid = path.playlistid
# db_playlist = PL.get_playlist_by_id(playlistid)
db_playlist = PlaylistTable.get_by_id(playlistid) db_playlist = PlaylistTable.get_by_id(playlistid)
if db_playlist is None: if db_playlist is None:
@@ -270,7 +261,6 @@ def update_playlist_info(path: PlaylistIDPath, form: UpdatePlaylistForm):
"last_updated": create_new_date(), "last_updated": create_new_date(),
"name": str(form.name).strip(), "name": str(form.name).strip(),
"settings": settings, "settings": settings,
"trackhashes": json.dumps([]),
} }
if image: if image:
@@ -290,7 +280,6 @@ def update_playlist_info(path: PlaylistIDPath, form: UpdatePlaylistForm):
p_tuple = (*playlist.values(),) p_tuple = (*playlist.values(),)
# PL.update_playlist(playlistid, playlist)
PlaylistTable.update_one(playlistid, playlist) PlaylistTable.update_one(playlistid, playlist)
playlist = models.Playlist(*p_tuple) playlist = models.Playlist(*p_tuple)
@@ -306,8 +295,6 @@ def pin_unpin_playlist(path: PlaylistIDPath):
""" """
Pin playlist. Pin playlist.
""" """
# playlist = PL.get_playlist_by_id(path.playlistid)
playlist = PlaylistTable.get_by_id(path.playlistid) playlist = PlaylistTable.get_by_id(path.playlistid)
if playlist is None: if playlist is None:
@@ -320,7 +307,6 @@ def pin_unpin_playlist(path: PlaylistIDPath):
except KeyError: except KeyError:
settings["pinned"] = True settings["pinned"] = True
# PL.update_settings(path.playlistid, settings)
PlaylistTable.update_settings(path.playlistid, settings) PlaylistTable.update_settings(path.playlistid, settings)
return {"msg": "Done"}, 200 return {"msg": "Done"}, 200
@@ -330,13 +316,11 @@ def remove_playlist_image(path: PlaylistIDPath):
""" """
Clear playlist image. Clear playlist image.
""" """
# playlist = PL.get_playlist_by_id(path.playlistid)
playlist = PlaylistTable.get_by_id(path.playlistid) playlist = PlaylistTable.get_by_id(path.playlistid)
if playlist is None: if playlist is None:
return {"error": "Playlist not found"}, 404 return {"error": "Playlist not found"}, 404
# PL.remove_banner(path.playlistid)
PlaylistTable.remove_image(path.playlistid) PlaylistTable.remove_image(path.playlistid)
playlist.image = None playlist.image = None
@@ -355,7 +339,6 @@ def remove_playlist(path: PlaylistIDPath):
""" """
Delete playlist Delete playlist
""" """
# PL.delete_playlist(path.playlistid)
PlaylistTable.remove_one(path.playlistid) PlaylistTable.remove_one(path.playlistid)
return {"msg": "Done"}, 200 return {"msg": "Done"}, 200
@@ -378,7 +361,6 @@ def remove_tracks_from_playlist(
# index: int; # index: int;
# } # }
# PL.remove_tracks_from_playlist(path.playlistid, body.tracks)
PlaylistTable.remove_from_playlist(path.playlistid, body.tracks) PlaylistTable.remove_from_playlist(path.playlistid, body.tracks)
return {"msg": "Done"}, 200 return {"msg": "Done"}, 200
+23 -51
View File
@@ -2,16 +2,17 @@
Contains all the search routes. Contains all the search routes.
""" """
from flask import request
from unidecode import unidecode from unidecode import unidecode
from pydantic import BaseModel, Field from pydantic import Field
from flask_openapi3 import Tag from flask_openapi3 import Tag
from flask_openapi3 import APIBlueprint from flask_openapi3 import APIBlueprint
from app import models from app import models
from app.api.apischemas import GenericLimitSchema
from app.db.libdata import TrackTable
from app.lib import searchlib from app.lib import searchlib
from app.settings import Defaults from app.settings import Defaults
from app.store.tracks import TrackStore
tag = Tag(name="Search", description="Search for tracks, albums and artists") tag = Tag(name="Search", description="Search for tracks, albums and artists")
api = APIBlueprint("search", __name__, url_prefix="/search", abp_tags=[tag]) api = APIBlueprint("search", __name__, url_prefix="/search", abp_tags=[tag])
@@ -20,30 +21,18 @@ SEARCH_COUNT = 30
"""The max amount of items to return per request""" """The max amount of items to return per request"""
def query_in_quotes(query: str) -> bool:
"""
Returns True if the query is in quotes
"""
try:
return query.startswith('"') and query.endswith('"')
except AttributeError:
return False
class Search: class Search:
def __init__(self, query: str) -> None: def __init__(self, query: str) -> None:
self.tracks: list[models.Track] = [] self.tracks: list[models.Track] = []
self.query = unidecode(query) self.query = unidecode(query)
def search_tracks(self, in_quotes=False): def search_tracks(self):
""" """
Calls :class:`SearchTracks` which returns the tracks that fuzzily match Calls :class:`SearchTracks` which returns the tracks that fuzzily match
the search terms. Then adds them to the `SearchResults` store. the search terms. Then adds them to the `SearchResults` store.
""" """
self.tracks = TrackStore.tracks self.tracks = TrackTable.get_all()
return searchlib.TopResults().search( return searchlib.TopResults().search(self.query, tracks_only=True)
self.query, tracks_only=True, in_quotes=in_quotes
)
def search_artists(self): def search_artists(self):
"""Calls :class:`SearchArtists` which returns the artists that fuzzily match """Calls :class:`SearchArtists` which returns the artists that fuzzily match
@@ -51,25 +40,23 @@ class Search:
""" """
return searchlib.SearchArtists(self.query)() return searchlib.SearchArtists(self.query)()
def search_albums(self, in_quotes=False): def search_albums(self):
"""Calls :class:`SearchAlbums` which returns the albums that fuzzily match """Calls :class:`SearchAlbums` which returns the albums that fuzzily match
the search term. Then adds them to the `SearchResults` store. the search term. Then adds them to the `SearchResults` store.
""" """
return searchlib.TopResults().search( return searchlib.TopResults().search(self.query, albums_only=True)
self.query, albums_only=True, in_quotes=in_quotes
)
def get_top_results( def get_top_results(
self, self,
limit: int, limit: int,
in_quotes=False,
): ):
finder = searchlib.TopResults() finder = searchlib.TopResults()
return finder.search(self.query, in_quotes=in_quotes, limit=limit) return finder.search(self.query, limit=limit)
class SearchQuery(BaseModel): class SearchQuery(GenericLimitSchema):
q: str = Field(description="The search query", example=Defaults.API_ARTISTNAME) q: str = Field(description="The search query", example=Defaults.API_ARTISTNAME)
start: int = Field(description="The index to start from", default=0, example=0)
@api.get("/tracks") @api.get("/tracks")
@@ -77,11 +64,7 @@ def search_tracks(query: SearchQuery):
""" """
Search tracks Search tracks
""" """
tracks = Search(query.q).search_tracks()
query = query.q
in_quotes = query_in_quotes(query)
tracks = Search(query).search_tracks(in_quotes)
return { return {
"tracks": tracks[:SEARCH_COUNT], "tracks": tracks[:SEARCH_COUNT],
@@ -90,15 +73,13 @@ def search_tracks(query: SearchQuery):
@api.get("/albums") @api.get("/albums")
def search_albums(query: SearchQuery): def search_albums(
query: SearchQuery,
):
""" """
Search albums. Search albums.
""" """
albums = Search(query.q).search_albums()
query = query.q
in_quotes = query_in_quotes(query)
albums = Search(query).search_albums(in_quotes)
return { return {
"albums": albums[:SEARCH_COUNT], "albums": albums[:SEARCH_COUNT],
@@ -111,13 +92,10 @@ def search_artists(query: SearchQuery):
""" """
Search artists. Search artists.
""" """
if not query.q:
query = query.q
if not query:
return {"error": "No query provided"}, 400 return {"error": "No query provided"}, 400
artists = Search(query).search_artists() artists = Search(query.q).search_artists()
return { return {
"artists": artists[:SEARCH_COUNT], "artists": artists[:SEARCH_COUNT],
@@ -138,15 +116,10 @@ def get_top_results(query: TopResultsQuery):
Returns the top results for the given query. Returns the top results for the given query.
""" """
limit = query.limit if not query.q:
query = query.q
in_quotes = query_in_quotes(query)
if not query:
return {"error": "No query provided"}, 400 return {"error": "No query provided"}, 400
return Search(query).get_top_results(in_quotes=in_quotes, limit=limit) return Search(query.q).get_top_results(limit=query.limit)
class SearchLoadMoreQuery(SearchQuery): class SearchLoadMoreQuery(SearchQuery):
@@ -166,17 +139,16 @@ def search_load_more(query: SearchLoadMoreQuery):
query = query.q query = query.q
item_type = query.type item_type = query.type
index = query.index index = query.index
in_quotes = query_in_quotes(query)
if item_type == "tracks": if item_type == "tracks":
t = Search(query).search_tracks(in_quotes) t = Search(query).search_tracks()
return { return {
"tracks": t[index : index + SEARCH_COUNT], "tracks": t[index : index + SEARCH_COUNT],
"more": len(t) > index + SEARCH_COUNT, "more": len(t) > index + SEARCH_COUNT,
} }
elif item_type == "albums": elif item_type == "albums":
a = Search(query).search_albums(in_quotes) a = Search(query).search_albums()
return { return {
"albums": a[index : index + SEARCH_COUNT], "albums": a[index : index + SEARCH_COUNT],
"more": len(a) > index + SEARCH_COUNT, "more": len(a) > index + SEARCH_COUNT,
+1 -1
View File
@@ -73,7 +73,7 @@ def send_file_as_chunks(filepath: str, audio_type: str) -> Response:
""" """
# NOTE: +1 makes sure the last byte is included in the range. # NOTE: +1 makes sure the last byte is included in the range.
# NOTE: -1 is used to convert the end index to a 0-based index. # NOTE: -1 is used to convert the end index to a 0-based index.
chunk_size = 1024 * 360 # 360 KB chunk_size = 1024 * 512 # 0.5MB
# Get file size # Get file size
file_size = os.path.getsize(filepath) file_size = os.path.getsize(filepath)
+50 -20
View File
@@ -14,7 +14,7 @@ from app.models import Album as AlbumModel
from app.utils.remove_duplicates import remove_duplicates from app.utils.remove_duplicates import remove_duplicates
from app.db import engine from app.db import engine
from sqlalchemy import JSON, Boolean, Integer, String, and_, delete, select, update from sqlalchemy import JSON, Boolean, Integer, String, delete, select, update
from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase
@@ -32,16 +32,26 @@ class Base(MasterBase, DeclarativeBase):
@classmethod @classmethod
def get_all_hashes(cls, create_date: int | None = None): def get_all_hashes(cls, create_date: int | None = None):
with DbManager() as conn: with DbManager() as conn:
if cls.__tablename__ == "track": if create_date:
stmt = select(TrackTable.trackhash).where(cls.last_mod < create_date) if cls.__tablename__ == "track":
elif cls.__tablename__ == "album": stmt = select(TrackTable.trackhash).where(
stmt = select(AlbumTable.albumhash).where( cls.last_mod < create_date
cls.created_date < create_date )
) elif cls.__tablename__ == "album":
elif cls.__tablename__ == "artist": stmt = select(AlbumTable.albumhash).where(
stmt = select(ArtistTable.artisthash).where( cls.created_date < create_date
cls.created_date < create_date )
) elif cls.__tablename__ == "artist":
stmt = select(ArtistTable.artisthash).where(
cls.created_date < create_date
)
else:
if cls.__tablename__ == "track":
stmt = select(TrackTable.trackhash)
elif cls.__tablename__ == "album":
stmt = select(AlbumTable.albumhash)
elif cls.__tablename__ == "artist":
stmt = select(ArtistTable.artisthash)
result = conn.execute(stmt) result = conn.execute(stmt)
return {row[0] for row in result.fetchall()} return {row[0] for row in result.fetchall()}
@@ -135,7 +145,9 @@ class TrackTable(Base):
def get_tracks_by_filepaths(cls, filepaths: list[str]): def get_tracks_by_filepaths(cls, filepaths: list[str]):
with DbManager() as conn: with DbManager() as conn:
result = conn.execute( result = conn.execute(
select(TrackTable).where(TrackTable.filepath.in_(filepaths)) select(TrackTable)
.where(TrackTable.filepath.in_(filepaths))
.order_by(TrackTable.last_mod)
) )
return tracks_to_dataclasses(result.fetchall()) return tracks_to_dataclasses(result.fetchall())
@@ -155,10 +167,8 @@ class TrackTable(Base):
result = conn.execute( result = conn.execute(
select(TrackTable) select(TrackTable)
.where( .where(
and_( (TrackTable.trackhash == hash)
TrackTable.trackhash == hash, & (TrackTable.filepath == filepath),
TrackTable.filepath == filepath,
)
) )
.order_by(TrackTable.bitrate.desc()) .order_by(TrackTable.bitrate.desc())
) )
@@ -194,9 +204,18 @@ class TrackTable(Base):
def get_tracks_by_trackhashes(cls, hashes: Iterable[str], limit: int | None = None): def get_tracks_by_trackhashes(cls, hashes: Iterable[str], limit: int | None = None):
with DbManager() as conn: with DbManager() as conn:
result = conn.execute( result = conn.execute(
select(TrackTable).where(TrackTable.trackhash.in_(hashes)).limit(limit) select(TrackTable)
.where(TrackTable.trackhash.in_(hashes))
.group_by(TrackTable.trackhash)
.limit(limit)
) )
return tracks_to_dataclasses(result.fetchall()) tracks = tracks_to_dataclasses(result.fetchall())
# order the tracks in the same order as the hashes
if type(hashes) == list:
return sorted(tracks, key=lambda x: hashes.index(x.trackhash))
return tracks
@classmethod @classmethod
def get_recently_added(cls, start: int, limit: int): def get_recently_added(cls, start: int, limit: int):
@@ -212,7 +231,12 @@ class TrackTable(Base):
@classmethod @classmethod
def get_recently_played(cls, limit: int): def get_recently_played(cls, limit: int):
result = cls.execute(select(cls).order_by(cls.lastplayed.desc()).limit(limit)) result = cls.execute(
select(cls)
.group_by(cls.trackhash)
.order_by(cls.lastplayed.desc())
.limit(limit)
)
return tracks_to_dataclasses(result.fetchall()) return tracks_to_dataclasses(result.fetchall())
@classmethod @classmethod
@@ -276,7 +300,13 @@ class AlbumTable(Base):
result = conn.execute( result = conn.execute(
select(AlbumTable).where(AlbumTable.albumhash.in_(hashes)).limit(limit) select(AlbumTable).where(AlbumTable.albumhash.in_(hashes)).limit(limit)
) )
return albums_to_dataclasses(result.fetchall()) albums = albums_to_dataclasses(result.fetchall())
# order the albums in the same order as the hashes
if type(hashes) == list:
return sorted(albums, key=lambda x: hashes.index(x.albumhash))
return albums
@classmethod @classmethod
def get_albums_by_artisthashes(cls, artisthashes: list[str]): def get_albums_by_artisthashes(cls, artisthashes: list[str]):
+50 -14
View File
@@ -1,7 +1,7 @@
import datetime import datetime
import enum
from shlex import join from shlex import join
from typing import Any from typing import Any
from flask_jwt_extended import current_user
from sqlalchemy import ( from sqlalchemy import (
JSON, JSON,
Boolean, Boolean,
@@ -313,36 +313,41 @@ class PlaylistTable(Base):
@classmethod @classmethod
def append_to_playlist(cls, id: int, trackhashes: list[str]): def append_to_playlist(cls, id: int, trackhashes: list[str]):
print("type(trackhashes):", type(trackhashes)) dbtrackhashes = cls.get_trackhashes(id)
if not dbtrackhashes:
dbtrackhashes = []
return cls.execute( return cls.execute(
update(cls) update(cls)
.where((cls.id == id) & (cls.userid == get_current_userid())) .where((cls.id == id) & (cls.userid == get_current_userid()))
.values(trackhashes=cls.trackhashes + trackhashes), .values(trackhashes=dbtrackhashes + trackhashes),
commit=True, commit=True,
) )
@classmethod @classmethod
def remove_from_playlist(cls, id: int, trackhashes: list[dict[str, Any]]): def get_trackhashes(cls, id: int) -> list[str]:
# CHECKPOINT: Properly remove tracks from a playlist result = cls.execute(
# Without messing up the order in case of duplicates
tracks = cls.execute(
select(cls.trackhashes).where( select(cls.trackhashes).where(
(cls.id == id) & (cls.userid == get_current_userid()) (cls.id == id) & (cls.userid == get_current_userid())
) )
) )
result = result.fetchone()
if result:
return result[0]
results = tracks.fetchone() @classmethod
if results: def remove_from_playlist(cls, id: int, trackhashes: list[dict[str, Any]]):
dbhashes: list[str] = results[0] # INFO: Get db trackhashes
dbtrackhashes = cls.get_trackhashes(id)
if dbtrackhashes:
for item in trackhashes: for item in trackhashes:
if dbhashes.index(item["trackhash"]) == item["index"]: if dbtrackhashes.index(item["trackhash"]) == item["index"]:
dbhashes.remove(item["trackhash"]) dbtrackhashes.remove(item["trackhash"])
return cls.execute( return cls.execute(
update(cls) update(cls)
.where((cls.id == id) & (cls.userid == get_current_userid())) .where((cls.id == id) & (cls.userid == get_current_userid()))
.values(trackhashes=dbhashes), .values(trackhashes=dbtrackhashes),
commit=True, commit=True,
) )
@@ -381,3 +386,34 @@ class PlaylistTable(Base):
.values(image=None), .values(image=None),
commit=True, commit=True,
) )
# class PlaylistTrackTable(Base):
# __tablename__ = "playlisttrack"
# id: Mapped[int] = mapped_column(primary_key=True)
# trackhash: Mapped[str] = mapped_column(String(), index=True)
# playlistid: Mapped[int] = mapped_column(
# Integer(), ForeignKey("playlist.id", ondelete="cascade")
# )
# index: Mapped[int] = mapped_column(Integer())
# userid: Mapped[int] = mapped_column(
# Integer(), ForeignKey("user.id", ondelete="cascade")
# )
# @classmethod
# def count_by_playlist()
# @classmethod
# def insert_many(cls, playlistid: int, trackhashes: list[str]):
# userid = get_current_userid()
# items = [
# {
# "index": index,
# "userid": userid,
# "trackhash": trackhash,
# "playlistid": playlistid,
# }
# for index, trackhash in enumerate(trackhashes)
# ]
# return cls.execute(insert(cls).values(items), commit=True)
+2 -10
View File
@@ -215,14 +215,6 @@ def get_recently_played(limit=7):
return items return items
def get_recently_played_tracks(limit: int):
# records = db.get_recently_played(start=0, limit=limit)
# last_updated = records[0].timestamp
# tracks = TrackStore.get_tracks_by_trackhashes([r.trackhash for r in records])
# return tracks, last_updated
return TrackTable.get_recently_played(limit)
def get_recently_played_playlist(limit: int = 100): def get_recently_played_playlist(limit: int = 100):
playlist = Playlist( playlist = Playlist(
id="recentlyplayed", id="recentlyplayed",
@@ -233,12 +225,12 @@ def get_recently_played_playlist(limit: int = 100):
trackhashes=[], trackhashes=[],
) )
tracks = get_recently_played_tracks(limit) tracks = TrackTable.get_recently_played(limit)
date = datetime.fromtimestamp(tracks[0].lastplayed) date = datetime.fromtimestamp(tracks[0].lastplayed)
playlist.last_updated = date_string_to_time_passed(create_new_date(date)) playlist.last_updated = date_string_to_time_passed(create_new_date(date))
images = get_first_4_images(tracks=tracks) images = get_first_4_images(tracks=tracks)
playlist.images = images playlist.images = images
playlist.set_count(len(tracks)) playlist.count = len(tracks)
return playlist, tracks return playlist, tracks
+38 -39
View File
@@ -8,16 +8,21 @@ from rapidfuzz import process, utils
from unidecode import unidecode from unidecode import unidecode
from app import models from app import models
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb from app.config import UserConfig
from app.db.libdata import AlbumTable, ArtistTable, TrackTable
# from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
from app.models.enums import FavType from app.models.enums import FavType
from app.models.track import Track from app.models.track import Track
from app.serializers.album import serialize_for_card as serialize_album from app.serializers.album import serialize_for_card as serialize_album
from app.serializers.album import serialize_for_card_many as serialize_albums from app.serializers.album import serialize_for_card_many as serialize_albums
from app.serializers.artist import serialize_for_cards from app.serializers.artist import serialize_for_cards
from app.serializers.track import serialize_track, serialize_tracks from app.serializers.track import serialize_track, serialize_tracks
from app.store.albums import AlbumStore
from app.store.artists import ArtistStore # from app.store.albums import AlbumStore
from app.store.tracks import TrackStore # from app.store.artists import ArtistStore
# from app.store.tracks import TrackStore
from app.utils.remove_duplicates import remove_duplicates from app.utils.remove_duplicates import remove_duplicates
# ratio = fuzz.ratio # ratio = fuzz.ratio
@@ -49,7 +54,8 @@ class Limit:
class SearchTracks: class SearchTracks:
def __init__(self, query: str) -> None: def __init__(self, query: str) -> None:
self.query = query self.query = query
self.tracks = TrackStore.tracks # self.tracks = TrackStore.tracks
self.tracks = TrackTable.get_all()
def __call__(self) -> List[models.Track]: def __call__(self) -> List[models.Track]:
""" """
@@ -72,7 +78,8 @@ class SearchTracks:
class SearchArtists: class SearchArtists:
def __init__(self, query: str) -> None: def __init__(self, query: str) -> None:
self.query = query self.query = query
self.artists = ArtistStore.artists # self.artists = ArtistStore.artists
self.artists = ArtistTable.get_all()
def __call__(self): def __call__(self):
""" """
@@ -94,7 +101,8 @@ class SearchArtists:
class SearchAlbums: class SearchAlbums:
def __init__(self, query: str) -> None: def __init__(self, query: str) -> None:
self.query = query self.query = query
self.albums = AlbumStore.albums # self.albums = AlbumStore.albums
self.albums = AlbumTable.get_all()
def __call__(self) -> List[models.Album]: def __call__(self) -> List[models.Album]:
""" """
@@ -137,7 +145,7 @@ _S2 = TypeVar("_S2")
_ResultType = int | float _ResultType = int | float
def get_titles(items: _type): def get_titles(items: list[_type]):
for item in items: for item in items:
if isinstance(item, models.Track): if isinstance(item, models.Track):
text = item.og_title text = item.og_title
@@ -161,9 +169,9 @@ class TopResults:
def collect_all(): def collect_all():
all_items: list[_type] = [] all_items: list[_type] = []
all_items.extend(ArtistStore.artists) all_items.extend(ArtistTable.get_all())
all_items.extend(TrackStore.tracks) all_items.extend(TrackTable.get_all())
all_items.extend(AlbumStore.albums) all_items.extend(AlbumTable.get_all())
return all_items, get_titles(all_items) return all_items, get_titles(all_items)
@@ -186,22 +194,16 @@ class TopResults:
return {"type": "track", "item": item} return {"type": "track", "item": item}
if isinstance(item, models.Album): if isinstance(item, models.Album):
tracks = TrackStore.get_tracks_by_albumhash(item.albumhash) tracks = TrackTable.get_tracks_by_albumhash(item.albumhash)
tracks = remove_duplicates(tracks) tracks = remove_duplicates(tracks)
item.get_date_from_tracks(tracks)
try: try:
item.duration = sum((t.duration for t in tracks)) item.duration = sum((t.duration for t in tracks))
except AttributeError: except AttributeError:
item.duration = 0 item.duration = 0
item.is_single(tracks) item.check_type(
tracks, singleTrackAsSingle=UserConfig().showAlbumsAsSingles
if not item.is_single:
item.check_type()
item.is_favorite = favdb.check_is_favorite(
item.albumhash, fav_type=FavType.album
) )
return {"type": "album", "item": item} return {"type": "album", "item": item}
@@ -210,15 +212,18 @@ class TopResults:
track_count = 0 track_count = 0
duration = 0 duration = 0
for track in TrackStore.get_tracks_by_artisthash(item.artisthash): tracks = TrackTable.get_tracks_by_artisthash(item.artisthash)
tracks = remove_duplicates(tracks)
for track in tracks:
track_count += 1 track_count += 1
duration += track.duration duration += track.duration
album_count = AlbumStore.count_albums_by_artisthash(item.artisthash) # album_count = AlbumStore.count_albums_by_artisthash(item.artisthash)
item.set_trackcount(track_count) # item.set_trackcount(track_count)
item.set_albumcount(album_count) # item.set_albumcount(album_count)
item.set_duration(duration) # item.set_duration(duration)
return {"type": "artist", "item": item} return {"type": "artist", "item": item}
@@ -230,7 +235,8 @@ class TopResults:
tracks.extend(SearchTracks(query)()) tracks.extend(SearchTracks(query)())
if item["type"] == "album": if item["type"] == "album":
t = TrackStore.get_tracks_by_albumhash(item["item"].albumhash) t = TrackTable.get_tracks_by_albumhash(item["item"].albumhash)
# t = TrackStore.get_tracks_by_albumhash(item["item"].albumhash)
t.sort(key=lambda x: x.last_mod) t.sort(key=lambda x: x.last_mod)
# if there are less than the limit, get more tracks # if there are less than the limit, get more tracks
@@ -242,7 +248,8 @@ class TopResults:
tracks.extend(t) tracks.extend(t)
if item["type"] == "artist": if item["type"] == "artist":
t = TrackStore.get_tracks_by_artisthash(item["item"].artisthash) # t = TrackStore.get_tracks_by_artisthash(item["item"].artisthash)
t = TrackTable.get_tracks_by_artisthash(item["item"].artisthash)
# if there are less than the limit, get more tracks # if there are less than the limit, get more tracks
if len(t) < limit: if len(t) < limit:
@@ -263,7 +270,8 @@ class TopResults:
return SearchAlbums(query)()[:limit] return SearchAlbums(query)()[:limit]
if item["type"] == "artist": if item["type"] == "artist":
albums = AlbumStore.get_albums_by_artisthash(item["item"].artisthash) # albums = AlbumStore.get_albums_by_artisthash(item["item"].artisthash)
albums = AlbumTable.get_albums_by_artisthash(item["item"].artisthash)
# if there are less than the limit, get more albums # if there are less than the limit, get more albums
if len(albums) < limit: if len(albums) < limit:
@@ -279,7 +287,6 @@ class TopResults:
limit: int = None, limit: int = None,
albums_only=False, albums_only=False,
tracks_only=False, tracks_only=False,
in_quotes=False,
): ):
items, titles = TopResults.collect_all() items, titles = TopResults.collect_all()
results = TopResults.get_results(titles, query) results = TopResults.get_results(titles, query)
@@ -307,21 +314,13 @@ class TopResults:
result = TopResults.map_with_type(result) result = TopResults.map_with_type(result)
if in_quotes: top_tracks = TopResults.get_track_items(result, query, limit=tracks_limit)
top_tracks = SearchTracks(query)()[:tracks_limit]
else:
top_tracks = TopResults.get_track_items(result, query, limit=tracks_limit)
top_tracks = serialize_tracks(top_tracks) top_tracks = serialize_tracks(top_tracks)
if tracks_only: if tracks_only:
return top_tracks return top_tracks
if in_quotes: albums = TopResults.get_album_items(result, query, limit=albums_limit)
albums = SearchAlbums(query)()[:albums_limit]
else:
albums = TopResults.get_album_items(result, query, limit=albums_limit)
albums = serialize_albums(albums) albums = serialize_albums(albums)
if albums_only: if albums_only:
+11 -6
View File
@@ -112,21 +112,26 @@ class Album:
Runs all the checks to determine the type of album. Runs all the checks to determine the type of album.
""" """
if self.is_single(tracks, singleTrackAsSingle): if self.is_single(tracks, singleTrackAsSingle):
return "single" self.type = "single"
return
if self.is_soundtrack(): if self.is_soundtrack():
return "soundtrack" self.type = "soundtrack"
return
if self.is_live_album(): if self.is_live_album():
return "live album" self.type = "live album"
return
if self.is_compilation(): if self.is_compilation():
return "compilation" self.type = "compilation"
return
if self.is_ep(): if self.is_ep():
return "ep" self.type = "ep"
return
return "album" self.type = "album"
def is_soundtrack(self) -> bool: def is_soundtrack(self) -> bool:
""" """
+6 -12
View File
@@ -5,6 +5,7 @@ from pathlib import Path
from typing import Any from typing import Any
from app import settings from app import settings
from app.utils.auth import get_current_userid
@dataclass(slots=True) @dataclass(slots=True)
@@ -16,10 +17,10 @@ class Playlist:
last_updated: str last_updated: str
name: str name: str
settings: dict settings: dict
userid: int trackhashes: list[str] = dataclasses.field(default_factory=list)
trackhashes: list[str]
extra: dict[str, Any] = dataclasses.field(default_factory=dict) extra: dict[str, Any] = dataclasses.field(default_factory=dict)
userid: int | None = None
thumb: str = "" thumb: str = ""
count: int = 0 count: int = 0
duration: int = 0 duration: int = 0
@@ -28,11 +29,10 @@ class Playlist:
pinned: bool = False pinned: bool = False
def __post_init__(self): def __post_init__(self):
# self.trackhashes = json.loads(str(self.trackhashes)) self.count = len(self.trackhashes)
# self.count = len(self.trackhashes)
# if isinstance(self.settings, str): if self.userid is None:
# self.settings = dict(json.loads(self.settings)) self.userid = get_current_userid()
self.pinned = self.settings.get("pinned", False) self.pinned = self.settings.get("pinned", False)
self.has_image = ( self.has_image = (
@@ -45,12 +45,6 @@ class Playlist:
self.image = "None" self.image = "None"
self.thumb = "None" self.thumb = "None"
# def set_duration(self, duration: int):
# self.duration = duration
# def set_count(self, count: int):
# self.count = count
def clear_lists(self): def clear_lists(self):
""" """
Removes data from lists to make it lighter for sending Removes data from lists to make it lighter for sending
+1 -1
View File
@@ -126,7 +126,7 @@ class Defaults:
SM_ARTIST_IMG_SIZE = 128 SM_ARTIST_IMG_SIZE = 128
MD_ARTIST_IMG_SIZE = 256 MD_ARTIST_IMG_SIZE = 256
HASH_LENGTH = 10 HASH_LENGTH = 16
API_ALBUMHASH = "bfe300e966" API_ALBUMHASH = "bfe300e966"
API_ARTISTHASH = "cae59f1fc5" API_ARTISTHASH = "cae59f1fc5"
API_TRACKHASH = "0853280a12" API_TRACKHASH = "0853280a12"
+2 -2
View File
@@ -1,5 +1,5 @@
import locale import locale
from typing import TypeVar from typing import Iterable, TypeVar
T = TypeVar("T") T = TypeVar("T")
@@ -16,5 +16,5 @@ def format_number(number: float) -> str:
def flatten(list_: list[list[T]]) -> list[T]: def flatten(list_: Iterable[list[T]]) -> list[T]:
return [item for sublist in list_ for item in sublist] return [item for sublist in list_ for item in sublist]
+8 -6
View File
@@ -1,4 +1,5 @@
import hashlib import hashlib
import xxhash
from unidecode import unidecode from unidecode import unidecode
@@ -32,11 +33,12 @@ def create_hash(*args: str, decode=False, limit=10) -> str:
str_ = unidecode(str_) str_ = unidecode(str_)
str_ = str_.encode("utf-8") str_ = str_.encode("utf-8")
str_ = hashlib.sha1(str_).hexdigest() return xxhash.xxh3_64(str_).hexdigest()
# str_ = hashlib.sha1(str_).hexdigest()
# INFO: Return first 5 + last 5 characters # INFO: Return first 5 + last 5 characters
return ( # return (
str_[: limit // 2] + str_[-limit // 2 :] # str_[: limit // 2] + str_[-limit // 2 :]
if limit % 2 == 0 # if limit % 2 == 0
else str_[: limit // 2] + str_[-limit // 2 - 1 :] # else str_[: limit // 2] + str_[-limit // 2 - 1 :]
) # )
Generated
+118 -1
View File
@@ -2542,6 +2542,123 @@ files = [
{file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"},
] ]
[[package]]
name = "xxhash"
version = "3.4.1"
description = "Python binding for xxHash"
optional = false
python-versions = ">=3.7"
files = [
{file = "xxhash-3.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:91dbfa55346ad3e18e738742236554531a621042e419b70ad8f3c1d9c7a16e7f"},
{file = "xxhash-3.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:665a65c2a48a72068fcc4d21721510df5f51f1142541c890491afc80451636d2"},
{file = "xxhash-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb11628470a6004dc71a09fe90c2f459ff03d611376c1debeec2d648f44cb693"},
{file = "xxhash-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bef2a7dc7b4f4beb45a1edbba9b9194c60a43a89598a87f1a0226d183764189"},
{file = "xxhash-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c0f7b2d547d72c7eda7aa817acf8791f0146b12b9eba1d4432c531fb0352228"},
{file = "xxhash-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00f2fdef6b41c9db3d2fc0e7f94cb3db86693e5c45d6de09625caad9a469635b"},
{file = "xxhash-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23cfd9ca09acaf07a43e5a695143d9a21bf00f5b49b15c07d5388cadf1f9ce11"},
{file = "xxhash-3.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6a9ff50a3cf88355ca4731682c168049af1ca222d1d2925ef7119c1a78e95b3b"},
{file = "xxhash-3.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f1d7c69a1e9ca5faa75546fdd267f214f63f52f12692f9b3a2f6467c9e67d5e7"},
{file = "xxhash-3.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:672b273040d5d5a6864a36287f3514efcd1d4b1b6a7480f294c4b1d1ee1b8de0"},
{file = "xxhash-3.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4178f78d70e88f1c4a89ff1ffe9f43147185930bb962ee3979dba15f2b1cc799"},
{file = "xxhash-3.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9804b9eb254d4b8cc83ab5a2002128f7d631dd427aa873c8727dba7f1f0d1c2b"},
{file = "xxhash-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c09c49473212d9c87261d22c74370457cfff5db2ddfc7fd1e35c80c31a8c14ce"},
{file = "xxhash-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ebbb1616435b4a194ce3466d7247df23499475c7ed4eb2681a1fa42ff766aff6"},
{file = "xxhash-3.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:25dc66be3db54f8a2d136f695b00cfe88018e59ccff0f3b8f545869f376a8a46"},
{file = "xxhash-3.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58c49083801885273e262c0f5bbeac23e520564b8357fbb18fb94ff09d3d3ea5"},
{file = "xxhash-3.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b526015a973bfbe81e804a586b703f163861da36d186627e27524f5427b0d520"},
{file = "xxhash-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36ad4457644c91a966f6fe137d7467636bdc51a6ce10a1d04f365c70d6a16d7e"},
{file = "xxhash-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:248d3e83d119770f96003271fe41e049dd4ae52da2feb8f832b7a20e791d2920"},
{file = "xxhash-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2070b6d5bbef5ee031666cf21d4953c16e92c2f8a24a94b5c240f8995ba3b1d0"},
{file = "xxhash-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2746035f518f0410915e247877f7df43ef3372bf36cfa52cc4bc33e85242641"},
{file = "xxhash-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a8ba6181514681c2591840d5632fcf7356ab287d4aff1c8dea20f3c78097088"},
{file = "xxhash-3.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aac5010869240e95f740de43cd6a05eae180c59edd182ad93bf12ee289484fa"},
{file = "xxhash-3.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4cb11d8debab1626181633d184b2372aaa09825bde709bf927704ed72765bed1"},
{file = "xxhash-3.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b29728cff2c12f3d9f1d940528ee83918d803c0567866e062683f300d1d2eff3"},
{file = "xxhash-3.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:a15cbf3a9c40672523bdb6ea97ff74b443406ba0ab9bca10ceccd9546414bd84"},
{file = "xxhash-3.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e66df260fed01ed8ea790c2913271641c58481e807790d9fca8bfd5a3c13844"},
{file = "xxhash-3.4.1-cp311-cp311-win32.whl", hash = "sha256:e867f68a8f381ea12858e6d67378c05359d3a53a888913b5f7d35fbf68939d5f"},
{file = "xxhash-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:200a5a3ad9c7c0c02ed1484a1d838b63edcf92ff538770ea07456a3732c577f4"},
{file = "xxhash-3.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:1d03f1c0d16d24ea032e99f61c552cb2b77d502e545187338bea461fde253583"},
{file = "xxhash-3.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c4bbba9b182697a52bc0c9f8ec0ba1acb914b4937cd4a877ad78a3b3eeabefb3"},
{file = "xxhash-3.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9fd28a9da300e64e434cfc96567a8387d9a96e824a9be1452a1e7248b7763b78"},
{file = "xxhash-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6066d88c9329ab230e18998daec53d819daeee99d003955c8db6fc4971b45ca3"},
{file = "xxhash-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93805bc3233ad89abf51772f2ed3355097a5dc74e6080de19706fc447da99cd3"},
{file = "xxhash-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64da57d5ed586ebb2ecdde1e997fa37c27fe32fe61a656b77fabbc58e6fbff6e"},
{file = "xxhash-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a97322e9a7440bf3c9805cbaac090358b43f650516486746f7fa482672593df"},
{file = "xxhash-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe750d512982ee7d831838a5dee9e9848f3fb440e4734cca3f298228cc957a6"},
{file = "xxhash-3.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fd79d4087727daf4d5b8afe594b37d611ab95dc8e29fe1a7517320794837eb7d"},
{file = "xxhash-3.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:743612da4071ff9aa4d055f3f111ae5247342931dedb955268954ef7201a71ff"},
{file = "xxhash-3.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b41edaf05734092f24f48c0958b3c6cbaaa5b7e024880692078c6b1f8247e2fc"},
{file = "xxhash-3.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:a90356ead70d715fe64c30cd0969072de1860e56b78adf7c69d954b43e29d9fa"},
{file = "xxhash-3.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac56eebb364e44c85e1d9e9cc5f6031d78a34f0092fea7fc80478139369a8b4a"},
{file = "xxhash-3.4.1-cp312-cp312-win32.whl", hash = "sha256:911035345932a153c427107397c1518f8ce456f93c618dd1c5b54ebb22e73747"},
{file = "xxhash-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:f31ce76489f8601cc7b8713201ce94b4bd7b7ce90ba3353dccce7e9e1fee71fa"},
{file = "xxhash-3.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:b5beb1c6a72fdc7584102f42c4d9df232ee018ddf806e8c90906547dfb43b2da"},
{file = "xxhash-3.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6d42b24d1496deb05dee5a24ed510b16de1d6c866c626c2beb11aebf3be278b9"},
{file = "xxhash-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b685fab18876b14a8f94813fa2ca80cfb5ab6a85d31d5539b7cd749ce9e3624"},
{file = "xxhash-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:419ffe34c17ae2df019a4685e8d3934d46b2e0bbe46221ab40b7e04ed9f11137"},
{file = "xxhash-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e041ce5714f95251a88670c114b748bca3bf80cc72400e9f23e6d0d59cf2681"},
{file = "xxhash-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc860d887c5cb2f524899fb8338e1bb3d5789f75fac179101920d9afddef284b"},
{file = "xxhash-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:312eba88ffe0a05e332e3a6f9788b73883752be63f8588a6dc1261a3eaaaf2b2"},
{file = "xxhash-3.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e01226b6b6a1ffe4e6bd6d08cfcb3ca708b16f02eb06dd44f3c6e53285f03e4f"},
{file = "xxhash-3.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9f3025a0d5d8cf406a9313cd0d5789c77433ba2004b1c75439b67678e5136537"},
{file = "xxhash-3.4.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:6d3472fd4afef2a567d5f14411d94060099901cd8ce9788b22b8c6f13c606a93"},
{file = "xxhash-3.4.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:43984c0a92f06cac434ad181f329a1445017c33807b7ae4f033878d860a4b0f2"},
{file = "xxhash-3.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a55e0506fdb09640a82ec4f44171273eeabf6f371a4ec605633adb2837b5d9d5"},
{file = "xxhash-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:faec30437919555b039a8bdbaba49c013043e8f76c999670aef146d33e05b3a0"},
{file = "xxhash-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c9e1b646af61f1fc7083bb7b40536be944f1ac67ef5e360bca2d73430186971a"},
{file = "xxhash-3.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:961d948b7b1c1b6c08484bbce3d489cdf153e4122c3dfb07c2039621243d8795"},
{file = "xxhash-3.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:719a378930504ab159f7b8e20fa2aa1896cde050011af838af7e7e3518dd82de"},
{file = "xxhash-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74fb5cb9406ccd7c4dd917f16630d2e5e8cbbb02fc2fca4e559b2a47a64f4940"},
{file = "xxhash-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5dab508ac39e0ab988039bc7f962c6ad021acd81fd29145962b068df4148c476"},
{file = "xxhash-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c59f3e46e7daf4c589e8e853d700ef6607afa037bfad32c390175da28127e8c"},
{file = "xxhash-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cc07256eff0795e0f642df74ad096f8c5d23fe66bc138b83970b50fc7f7f6c5"},
{file = "xxhash-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9f749999ed80f3955a4af0eb18bb43993f04939350b07b8dd2f44edc98ffee9"},
{file = "xxhash-3.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7688d7c02149a90a3d46d55b341ab7ad1b4a3f767be2357e211b4e893efbaaf6"},
{file = "xxhash-3.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a8b4977963926f60b0d4f830941c864bed16aa151206c01ad5c531636da5708e"},
{file = "xxhash-3.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8106d88da330f6535a58a8195aa463ef5281a9aa23b04af1848ff715c4398fb4"},
{file = "xxhash-3.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4c76a77dbd169450b61c06fd2d5d436189fc8ab7c1571d39265d4822da16df22"},
{file = "xxhash-3.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:11f11357c86d83e53719c592021fd524efa9cf024dc7cb1dfb57bbbd0d8713f2"},
{file = "xxhash-3.4.1-cp38-cp38-win32.whl", hash = "sha256:0c786a6cd74e8765c6809892a0d45886e7c3dc54de4985b4a5eb8b630f3b8e3b"},
{file = "xxhash-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:aabf37fb8fa27430d50507deeab2ee7b1bcce89910dd10657c38e71fee835594"},
{file = "xxhash-3.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6127813abc1477f3a83529b6bbcfeddc23162cece76fa69aee8f6a8a97720562"},
{file = "xxhash-3.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef2e194262f5db16075caea7b3f7f49392242c688412f386d3c7b07c7733a70a"},
{file = "xxhash-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71be94265b6c6590f0018bbf73759d21a41c6bda20409782d8117e76cd0dfa8b"},
{file = "xxhash-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10e0a619cdd1c0980e25eb04e30fe96cf8f4324758fa497080af9c21a6de573f"},
{file = "xxhash-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa122124d2e3bd36581dd78c0efa5f429f5220313479fb1072858188bc2d5ff1"},
{file = "xxhash-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17032f5a4fea0a074717fe33477cb5ee723a5f428de7563e75af64bfc1b1e10"},
{file = "xxhash-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca7783b20e3e4f3f52f093538895863f21d18598f9a48211ad757680c3bd006f"},
{file = "xxhash-3.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d77d09a1113899fad5f354a1eb4f0a9afcf58cefff51082c8ad643ff890e30cf"},
{file = "xxhash-3.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:21287bcdd299fdc3328cc0fbbdeaa46838a1c05391264e51ddb38a3f5b09611f"},
{file = "xxhash-3.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:dfd7a6cc483e20b4ad90224aeb589e64ec0f31e5610ab9957ff4314270b2bf31"},
{file = "xxhash-3.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:543c7fcbc02bbb4840ea9915134e14dc3dc15cbd5a30873a7a5bf66039db97ec"},
{file = "xxhash-3.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fe0a98d990e433013f41827b62be9ab43e3cf18e08b1483fcc343bda0d691182"},
{file = "xxhash-3.4.1-cp39-cp39-win32.whl", hash = "sha256:b9097af00ebf429cc7c0e7d2fdf28384e4e2e91008130ccda8d5ae653db71e54"},
{file = "xxhash-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:d699b921af0dcde50ab18be76c0d832f803034d80470703700cb7df0fbec2832"},
{file = "xxhash-3.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:2be491723405e15cc099ade1280133ccfbf6322d2ef568494fb7d07d280e7eee"},
{file = "xxhash-3.4.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:431625fad7ab5649368c4849d2b49a83dc711b1f20e1f7f04955aab86cd307bc"},
{file = "xxhash-3.4.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc6dbd5fc3c9886a9e041848508b7fb65fd82f94cc793253990f81617b61fe49"},
{file = "xxhash-3.4.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ff8dbd0ec97aec842476cb8ccc3e17dd288cd6ce3c8ef38bff83d6eb927817"},
{file = "xxhash-3.4.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef73a53fe90558a4096e3256752268a8bdc0322f4692ed928b6cd7ce06ad4fe3"},
{file = "xxhash-3.4.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:450401f42bbd274b519d3d8dcf3c57166913381a3d2664d6609004685039f9d3"},
{file = "xxhash-3.4.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a162840cf4de8a7cd8720ff3b4417fbc10001eefdd2d21541a8226bb5556e3bb"},
{file = "xxhash-3.4.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b736a2a2728ba45017cb67785e03125a79d246462dfa892d023b827007412c52"},
{file = "xxhash-3.4.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0ae4c2e7698adef58710d6e7a32ff518b66b98854b1c68e70eee504ad061d8"},
{file = "xxhash-3.4.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6322c4291c3ff174dcd104fae41500e75dad12be6f3085d119c2c8a80956c51"},
{file = "xxhash-3.4.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:dd59ed668801c3fae282f8f4edadf6dc7784db6d18139b584b6d9677ddde1b6b"},
{file = "xxhash-3.4.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:92693c487e39523a80474b0394645b393f0ae781d8db3474ccdcead0559ccf45"},
{file = "xxhash-3.4.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4603a0f642a1e8d7f3ba5c4c25509aca6a9c1cc16f85091004a7028607ead663"},
{file = "xxhash-3.4.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fa45e8cbfbadb40a920fe9ca40c34b393e0b067082d94006f7f64e70c7490a6"},
{file = "xxhash-3.4.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:595b252943b3552de491ff51e5bb79660f84f033977f88f6ca1605846637b7c6"},
{file = "xxhash-3.4.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:562d8b8f783c6af969806aaacf95b6c7b776929ae26c0cd941d54644ea7ef51e"},
{file = "xxhash-3.4.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:41ddeae47cf2828335d8d991f2d2b03b0bdc89289dc64349d712ff8ce59d0647"},
{file = "xxhash-3.4.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c44d584afdf3c4dbb3277e32321d1a7b01d6071c1992524b6543025fb8f4206f"},
{file = "xxhash-3.4.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd7bddb3a5b86213cc3f2c61500c16945a1b80ecd572f3078ddbbe68f9dabdfb"},
{file = "xxhash-3.4.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ecb6c987b62437c2f99c01e97caf8d25660bf541fe79a481d05732e5236719c"},
{file = "xxhash-3.4.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:696b4e18b7023527d5c50ed0626ac0520edac45a50ec7cf3fc265cd08b1f4c03"},
{file = "xxhash-3.4.1.tar.gz", hash = "sha256:0379d6cf1ff987cd421609a264ce025e74f346e3e145dd106c0cc2e3ec3f99a9"},
]
[[package]] [[package]]
name = "zope-event" name = "zope-event"
version = "5.0" version = "5.0"
@@ -2616,4 +2733,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.10,<3.12" python-versions = ">=3.10,<3.12"
content-hash = "80cb2755efc6cec2cb20d50cb8927dee554991741283e70e7a2665e6253b895d" content-hash = "184c1c56051131473212b74de5719e2629c630555b13cd4cd2de371b3a2fb195"
+1
View File
@@ -29,6 +29,7 @@ flask-jwt-extended = "^4.6.0"
sqlalchemy = "^2.0.31" sqlalchemy = "^2.0.31"
memory-profiler = "^0.61.0" memory-profiler = "^0.61.0"
sortedcontainers = "^2.4.0" sortedcontainers = "^2.4.0"
xxhash = "^3.4.1"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pylint = "^2.15.5" pylint = "^2.15.5"