mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
move to xxh3 hashing algorithm
+ port: search
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user