diff --git a/app/api/search.py b/app/api/search.py index 05fcafc9..f3dfe82b 100644 --- a/app/api/search.py +++ b/app/api/search.py @@ -11,6 +11,7 @@ from flask_openapi3 import APIBlueprint from app import models from app.api.apischemas import GenericLimitSchema from app.lib import searchlib +from app.serializers.artist import serialize_for_cards from app.settings import Defaults from app.store.tracks import TrackStore @@ -61,7 +62,8 @@ class Search: """Calls :class:`SearchArtists` which returns the artists that fuzzily match the search term. Then adds them to the `SearchResults` store. """ - return searchlib.SearchArtists(self.query)() + artists = searchlib.SearchArtists(self.query)() + return serialize_for_cards(artists) def search_albums(self): """Calls :class:`SearchAlbums` which returns the albums that fuzzily match diff --git a/app/db/userdata.py b/app/db/userdata.py index e34da242..c0884b15 100644 --- a/app/db/userdata.py +++ b/app/db/userdata.py @@ -519,16 +519,10 @@ class LibDataTable(Base): @classmethod def get_all_colors(cls, type: str) -> Iterable[dict[str, str]]: - result = cls.execute( - select(cls.itemhash, cls.color).where(cls.itemtype == type) - ) - # return [ - # {"itemhash": r[0].replace(type, ""), "color": r[1]} - # for r in result.fetchall() - # ] + result = cls.execute(select(cls).where(cls.itemtype == type)) for i in next(result).scalars(): - yield {"itemhash": i[0].replace(type, ""), "color": i[1]} + yield {"itemhash": i.itemhash.replace(type, ""), "color": i.color} class MixTable(Base): diff --git a/app/lib/colorlib.py b/app/lib/colorlib.py index 8d6b53f8..255a1967 100644 --- a/app/lib/colorlib.py +++ b/app/lib/colorlib.py @@ -113,6 +113,7 @@ class ProcessArtistColors: ) record = LibDataTable.find_one(artisthash, "artist") + print(record) if (record is not None) and (record.color is not None): continue diff --git a/app/lib/home/get_recently_played.py b/app/lib/home/get_recently_played.py index 79bde7d4..3637d013 100644 --- a/app/lib/home/get_recently_played.py +++ b/app/lib/home/get_recently_played.py @@ -39,9 +39,4 @@ def get_recently_played( iterations += 1 - if iterations == max_iterations: - print( - f"Warning: Reached maximum iterations ({max_iterations}) while fetching recently played items" - ) - return items \ No newline at end of file diff --git a/app/lib/searchlib.py b/app/lib/searchlib.py index d2bf65b9..2ae16762 100644 --- a/app/lib/searchlib.py +++ b/app/lib/searchlib.py @@ -4,7 +4,7 @@ This library contains all the functions related to the search functionality. from typing import Any, Generator, List, TypeVar -from rapidfuzz import process, utils +from rapidfuzz import process, utils, fuzz from unidecode import unidecode from app import models @@ -13,11 +13,14 @@ 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.album import Album +from app.models.artist import Artist from app.models.enums import FavType +from app.models.playlist import Playlist 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_many as serialize_albums -from app.serializers.artist import serialize_for_cards +from app.serializers.artist import serialize_for_card, serialize_for_cards from app.serializers.track import serialize_track, serialize_tracks from app.store.albums import AlbumStore @@ -35,10 +38,10 @@ class Cutoff: Holds all the default cutoff values. """ - tracks: int = 75 - albums: int = 75 - artists: int = 75 - playlists: int = 75 + tracks: int = 50 + albums: int = 50 + artists: int = 50 + playlists: int = 50 class Limit: @@ -57,21 +60,28 @@ class SearchTracks: self.query = query self.tracks = TrackStore.get_flat_list() - def __call__(self) -> List[models.Track]: + def __call__(self, limit: int = Limit.tracks) -> List[models.Track]: """ Gets all songs with a given title. """ - track_titles = [unidecode(track.og_title).lower() for track in self.tracks] + track_titles = [unidecode(track.title).lower() for track in self.tracks] results = process.extract( self.query, track_titles, score_cutoff=Cutoff.tracks, - limit=Limit.tracks, + limit=limit, processor=utils.default_process, + scorer=fuzz.WRatio, ) - tracks = [self.tracks[i[2]] for i in results] + tracks: list[Track] = [] + + for item in results: + track = self.tracks[item[2]] + track._score = item[1] + tracks.append(track) + return remove_duplicates(tracks) @@ -80,21 +90,29 @@ class SearchArtists: self.query = query self.artists = ArtistStore.get_flat_list() - def __call__(self): + def __call__(self, limit: int = Limit.artists): """ Gets all artists with a given name. """ - artists = [unidecode(a.name).lower() for a in self.artists] + choices = [unidecode(a.name).lower() for a in self.artists] results = process.extract( self.query, - artists, + choices, score_cutoff=Cutoff.artists, - limit=Limit.artists, + limit=limit, processor=utils.default_process, + scorer=fuzz.WRatio, ) - return [self.artists[i[2]] for i in results] + artists: list[Artist] = [] + + for item in results: + artist = self.artists[item[2]] + artist._score = item[1] + artists.append(artist) + + return artists class SearchAlbums: @@ -102,22 +120,30 @@ class SearchAlbums: self.query = query self.albums = AlbumStore.get_flat_list() - def __call__(self) -> List[models.Album]: + def __call__(self, limit: int = Limit.albums): """ Gets all albums with a given title. """ - albums = [unidecode(a.og_title).lower() for a in self.albums] + choices = [unidecode(a.title).lower() for a in self.albums] results = process.extract( self.query, - albums, + choices, score_cutoff=Cutoff.albums, - limit=Limit.albums, + limit=limit, processor=utils.default_process, + scorer=fuzz.token_sort_ratio, ) - return [self.albums[i[2]] for i in results] + albums: list[Album] = [] + + for item in results: + album = self.albums[item[2]] + album._score = item[1] + albums.append(album) + + return albums class SearchPlaylists: @@ -125,22 +151,28 @@ class SearchPlaylists: self.playlists = playlists self.query = query - def __call__(self) -> List[models.Playlist]: - playlists = [p.name for p in self.playlists] + def __call__(self, limit: int = Limit.playlists): + choices = [p.name for p in self.playlists] results = process.extract( self.query, - playlists, + choices, score_cutoff=Cutoff.playlists, - limit=Limit.playlists, + limit=limit, processor=utils.default_process, + scorer=fuzz.WRatio, ) - return [self.playlists[i[2]] for i in results] + playlists: list[Playlist] = [] + + for item in results: + playlist = self.playlists[item[2]] + playlist._score = item[1] + playlists.append(playlist) + + return playlists _type = models.Track | models.Album | models.Artist -_S2 = TypeVar("_S2") -_ResultType = int | float def get_titles(items: list[_type]): @@ -169,108 +201,45 @@ class TopResults: all_items.extend(ArtistStore.get_flat_list()) all_items.extend(TrackStore.get_flat_list()) - all_items.extend(TrackStore.get_flat_list()) + all_items.extend(AlbumStore.get_flat_list()) return all_items, get_titles(all_items) @staticmethod - def get_results(items: Generator[str, Any, None], query: str): - items = list(items) - - results = process.extract( - query=query, choices=items, score_cutoff=Cutoff.tracks, limit=1 - ) - - return results - - @staticmethod - def map_with_type(item: _type): - """ - Map the results to their respective types. - """ - if isinstance(item, models.Track): - return {"type": "track", "item": item} - - if isinstance(item, models.Album): - tracks = TrackStore.get_tracks_by_albumhash(item.albumhash) - tracks = remove_duplicates(tracks) - - try: - item.duration = sum((t.duration for t in tracks)) - except AttributeError: - item.duration = 0 - - item.check_type( - tracks, singleTrackAsSingle=UserConfig().showAlbumsAsSingles - ) - - return {"type": "album", "item": item} - - if isinstance(item, models.Artist): - track_count = 0 - duration = 0 - - tracks = TrackStore.get_tracks_by_artisthash(item.artisthash) - tracks = remove_duplicates(tracks) - - for track in tracks: - track_count += 1 - duration += track.duration - - return {"type": "artist", "item": item} - - @staticmethod - def get_track_items(item: dict[str, _type], query: str, limit=5): + def get_track_items(item: Track | Album | Artist, limit=5): tracks: list[Track] = [] - if item["type"] == "track": - tracks.extend(SearchTracks(query)()) + # INFO: If the item is a track, return empty list + # to be filled by the results from the top search + if isinstance(item, Track): + return tracks - if item["type"] == "album": - t = TrackStore.get_tracks_by_albumhash(item["item"].albumhash) - t.sort(key=lambda x: x.last_mod) + # INFO: If the item is an album, get the tracks from the album + if isinstance(item, Album): + tracks = TrackStore.get_tracks_by_albumhash(item.albumhash)[:limit] + tracks.sort(key=lambda x: x.playduration, reverse=True) + return tracks - # if there are less than the limit, get more tracks - if len(t) < limit: - remainder = limit - len(t) - more_tracks = SearchTracks(query)() - t.extend(more_tracks[:remainder]) + # INFO: If the item is an artist, get the tracks from the artist + if isinstance(item, Artist): + tracks = TrackStore.get_tracks_by_artisthash(item.artisthash)[:limit] + tracks.sort(key=lambda x: x.playduration, reverse=True) - tracks.extend(t) - - if item["type"] == "artist": - # t = TrackStore.get_tracks_by_artisthash(item["item"].artisthash) - t = TrackStore.get_tracks_by_artisthash(item["item"].artisthash) - - # if there are less than the limit, get more tracks - if len(t) < limit: - remainder = limit - len(t) - more_tracks = SearchTracks(query)() - t.extend(more_tracks[:remainder]) - - tracks.extend(t) - - return tracks[:limit] + return tracks @staticmethod - def get_album_items(item: dict[str, _type], query: str, limit=6): - if item["type"] == "track": - return SearchAlbums(query)()[:limit] + def get_album_items(item: Track | Album | Artist, limit=6): + albums: list[Album] = [] - if item["type"] == "album": - return SearchAlbums(query)()[:limit] + # INFO: If the item is a track or album, search for albums + if isinstance(item, Track) or isinstance(item, Album): + return albums - if item["type"] == "artist": - # albums = AlbumStore.get_albums_by_artisthash(item["item"].artisthash) - albums = AlbumStore.get_albums_by_artisthash(item["item"].artisthash) + # INFO: If the item is an artist, get the albums from the artist + if isinstance(item, Artist): + albums = AlbumStore.get_albums_by_artisthash(item.artisthash)[:limit] - # if there are less than the limit, get more albums - if len(albums) < limit: - remainder = limit - len(albums) - more_albums = SearchAlbums(query)() - albums.extend(more_albums[:remainder]) - - return albums[:limit] + return albums @staticmethod def search( @@ -279,56 +248,77 @@ class TopResults: albums_only=False, tracks_only=False, ): - items, titles = TopResults.collect_all() - results = TopResults.get_results(titles, query) - tracks_limit = Limit.tracks if tracks_only else 4 albums_limit = Limit.albums if albums_only else limit artists_limit = limit - # map results to their respective items - try: - result = [items[i[2]] for i in results][0] - except IndexError: + # INFO: Individually search all stores as each type has a different scorer + tracks = SearchTracks(query)(limit=tracks_limit) if not albums_only else [] + albums = SearchAlbums(query)(limit=albums_limit) + artists = SearchArtists(query)(limit=artists_limit) + + # INFO: Combine all results and sort them by score + all_results = artists + tracks + albums + all_results = sorted(all_results, key=lambda x: int(x._score), reverse=True) + + # INFO: Get the top result + top_result = all_results[0] + top_tracks = [] + + if not albums_only: + top_tracks = TopResults.get_track_items(top_result, limit=tracks_limit) + + # INFO: If there are not enough tracks, fill with search results + if len(top_tracks) < tracks_limit: + found_tracks_set = {track.trackhash for track in top_tracks} + + for track in tracks: + if track.trackhash not in found_tracks_set: + top_tracks.append(track) + + if len(top_tracks) >= tracks_limit: + break + + top_tracks = serialize_tracks(top_tracks) + if tracks_only: - return [] + return top_tracks - if albums_only: - return [] + top_albums = TopResults.get_album_items(top_result, limit=albums_limit) - return { - "top_result": None, - "tracks": [], - "artists": [], - "albums": [], - } + # INFO: If there are not enough albums, fill with search results + if len(top_albums) < albums_limit: + found_albums_set = {album.albumhash for album in top_albums} - result = TopResults.map_with_type(result) + for album in albums: + if album.albumhash not in found_albums_set: + top_albums.append(album) - top_tracks = TopResults.get_track_items(result, query, limit=tracks_limit) - top_tracks = serialize_tracks(top_tracks) + if len(top_albums) >= albums_limit: + break - if tracks_only: - return top_tracks - - albums = TopResults.get_album_items(result, query, limit=albums_limit) - albums = serialize_albums(albums) + top_albums = serialize_albums(top_albums) if albums_only: - return albums + return top_albums - artists = SearchArtists(query)()[:artists_limit] artists = serialize_for_cards(artists) - if result["type"] == "track": - result["item"] = serialize_track(result["item"]) + if isinstance(top_result, Track): + top_result = serialize_track(top_result) + top_result["type"] = "track" - if result["type"] == "album": - result["item"] = serialize_album(result["item"]) + if isinstance(top_result, Album): + top_result = serialize_album(top_result) + top_result["type"] = "album" + + if isinstance(top_result, Artist): + top_result = serialize_for_card(top_result) + top_result["type"] = "artist" return { - "top_result": result, + "top_result": top_result, "tracks": top_tracks, "artists": artists, - "albums": albums, + "albums": top_albums, } diff --git a/app/models/album.py b/app/models/album.py index 5a5714bc..f835b6e0 100644 --- a/app/models/album.py +++ b/app/models/album.py @@ -35,6 +35,7 @@ class Album: id: int = -1 type: str = "album" image: str = "" + _score: float = 0 versions: list[str] = dataclasses.field(default_factory=list) fav_userids: list[int] = dataclasses.field(default_factory=list) weakhash: str = "" diff --git a/app/models/artist.py b/app/models/artist.py index 59ae76f4..7284f81f 100644 --- a/app/models/artist.py +++ b/app/models/artist.py @@ -54,6 +54,7 @@ class Artist: id: int = -1 image: str = "" + _score: float = 0 color: str = "" fav_userids: list[int] = dataclasses.field(default_factory=list) diff --git a/app/models/playlist.py b/app/models/playlist.py index 47423f29..56c6d1f0 100644 --- a/app/models/playlist.py +++ b/app/models/playlist.py @@ -27,7 +27,7 @@ class Playlist: has_image: bool = False images: list[dict[str, str]] = dataclasses.field(default_factory=list) pinned: bool = False - + _score: float = 0 def __post_init__(self): self.count = len(self.trackhashes) diff --git a/app/models/track.py b/app/models/track.py index 9d6958ba..13263efd 100644 --- a/app/models/track.py +++ b/app/models/track.py @@ -50,6 +50,7 @@ class Track: _pos: int = 0 _ati: str = "" image: str = "" + _score: float = 0 explicit: bool = False fav_userids: list[int] = field(default_factory=list) diff --git a/pyproject.toml b/pyproject.toml index 72ae7561..add46605 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ dependencies = [ "requests>=2.27.1", "colorgram.py>=1.2.0", "tqdm>=4.65.0", - "rapidfuzz==2.13.7", "tinytag>=2.0.0", "Unidecode>=1.3.6", "psutil>=5.9.4", @@ -33,6 +32,7 @@ dependencies = [ "schedule>=1.2.2", "pillow>=11.1.0", "flask-openapi3==3.0.2", + "rapidfuzz==3.11.0", ] [dependency-groups] diff --git a/uv.lock b/uv.lock index 2b5738cf..e6b4bd60 100644 --- a/uv.lock +++ b/uv.lock @@ -1004,25 +1004,55 @@ wheels = [ [[package]] name = "rapidfuzz" -version = "2.13.7" +version = "3.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/e5/2ab8375be6955aff1925b69c41429cbe54e32a67461c0b59f94c9b8b1cc5/rapidfuzz-2.13.7.tar.gz", hash = "sha256:8d3e252d4127c79b4d7c2ae47271636cbaca905c8bb46d80c7930ab906cf4b5c", size = 999972 } +sdist = { url = "https://files.pythonhosted.org/packages/a4/aa/25e5a20131369d82c7b8288c99c2c3011ec47a3f5953ccc9cb8145720be5/rapidfuzz-3.11.0.tar.gz", hash = "sha256:a53ca4d3f52f00b393fab9b5913c5bafb9afc27d030c8a1db1283da6917a860f", size = 57983000 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/96/a6795d11e94af1563730b9363ca44a410a3e187fd42b93a27a8cbd6881dc/rapidfuzz-2.13.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b34e8c0e492949ecdd5da46a1cfc856a342e2f0389b379b1a45a3cdcd3176a6e", size = 2442563 }, - { url = "https://files.pythonhosted.org/packages/6f/5b/ba0a8497a2e154d963c60bac2e2ada81c35899e8c1fe732e3de720a5154e/rapidfuzz-2.13.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:875d51b3497439a72e2d76183e1cb5468f3f979ab2ddfc1d1f7dde3b1ecfb42f", size = 1825126 }, - { url = "https://files.pythonhosted.org/packages/38/c3/94522653c741b716ca5e1380ff5b43bbb738f795d624fa67d70ba3e3f64c/rapidfuzz-2.13.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ae33a72336059213996fe4baca4e0e4860913905c2efb7c991eab33b95a98a0a", size = 1091641 }, - { url = "https://files.pythonhosted.org/packages/58/dd/29d1790a6c7c7034593dfda7b67a4d6b6a20cfe9e7def8e0cf325d8abc2c/rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5585189b3d90d81ccd62d4f18530d5ac8972021f0aaaa1ffc6af387ff1dce75", size = 1478821 }, - { url = "https://files.pythonhosted.org/packages/09/fa/0b5ec7ab4f4641822e2df585bafb2579327b95822c0b26bc5bf8d8ce25cb/rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42085d4b154a8232767de8296ac39c8af5bccee6b823b0507de35f51c9cbc2d7", size = 2308161 }, - { url = "https://files.pythonhosted.org/packages/2a/90/8407b7ff902e660c98406bf9cec4adbbd873aa6ccbf2b14228fc7093535d/rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:585206112c294e335d84de5d5f179c0f932837752d7420e3de21db7fdc476278", size = 1780561 }, - { url = "https://files.pythonhosted.org/packages/c9/9f/27ed8be4fc73ff0c229a6b859f8365e293a2302ed3053facee4878c1639d/rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f891b98f8bc6c9d521785816085e9657212621e93f223917fb8e32f318b2957e", size = 1762281 }, - { url = "https://files.pythonhosted.org/packages/c0/f1/61d389cd067cf1c9494c232e19223f0ba3fa6c5fef9f0496f67b277c8f43/rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08590905a95ccfa43f4df353dcc5d28c15d70664299c64abcad8721d89adce4f", size = 2207652 }, - { url = "https://files.pythonhosted.org/packages/c9/65/e9d9484ebe787ce91b19235155493bb6b985db12fefee817b82c73d18d7d/rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b5dd713a1734574c2850c566ac4286594bacbc2d60b9170b795bee4b68656625", size = 1799606 }, - { url = "https://files.pythonhosted.org/packages/82/80/6995b618baab256652ec449e3d0a2b10972da5677d7adf78b2c7dae8237f/rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:988f8f6abfba7ee79449f8b50687c174733b079521c3cc121d65ad2d38831846", size = 2483027 }, - { url = "https://files.pythonhosted.org/packages/37/41/6405ce463e70ed8dfc28a762af19df560ea513555a3f0546d4e244b36b9b/rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b3210869161a864f3831635bb13d24f4708c0aa7208ef5baac1ac4d46e9b4208", size = 2026163 }, - { url = "https://files.pythonhosted.org/packages/31/c7/ff7b2d3fc6a32c4e0b914f11dde85b0e5875faa49fef57c1a09810d595d1/rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f6fe570e20e293eb50491ae14ddeef71a6a7e5f59d7e791393ffa99b13f1f8c2", size = 2048916 }, - { url = "https://files.pythonhosted.org/packages/ed/cc/4730f99586753877eed251bd276f1d95056b9431a14f53735a1bd47336f9/rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6120f2995f5154057454c5de99d86b4ef3b38397899b5da1265467e8980b2f60", size = 2421895 }, - { url = "https://files.pythonhosted.org/packages/90/3d/8507db522fbdeb9a802fe92fa980aa5e0bdb10d047d09db3ffc924d564a7/rapidfuzz-2.13.7-cp311-cp311-win32.whl", hash = "sha256:b20141fa6cee041917801de0bab503447196d372d4c7ee9a03721b0a8edf5337", size = 917098 }, - { url = "https://files.pythonhosted.org/packages/6c/62/643f0cf5430b601f17ea3671ad2d0e0a285b6ff82f2d83eab196091ef8bc/rapidfuzz-2.13.7-cp311-cp311-win_amd64.whl", hash = "sha256:ec55a81ac2b0f41b8d6fb29aad16e55417036c7563bad5568686931aa4ff08f7", size = 1036221 }, + { url = "https://files.pythonhosted.org/packages/40/ac/9ca008834104ad138fbfe2d7ae4443ada55e00c4eb4272d288897e8763b8/rapidfuzz-3.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8724a978f8af7059c5323d523870bf272a097478e1471295511cf58b2642ff83", size = 1955019 }, + { url = "https://files.pythonhosted.org/packages/4c/55/d026c01c9312c9c2a413679052a9bb884743fc5655e59339116d83a2125b/rapidfuzz-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b63cb1f2eb371ef20fb155e95efd96e060147bdd4ab9fc400c97325dfee9fe1", size = 1427753 }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5f3fae81dd1efdf47da19641e321ae84b4f49a5a7b2ab3ff78bd04a0ae7f/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82497f244aac10b20710448645f347d862364cc4f7d8b9ba14bd66b5ce4dec18", size = 1411472 }, + { url = "https://files.pythonhosted.org/packages/3c/3f/770b0fca00faf42983fe21fbd379f429dc2600c58d7015f969fb1f73c1db/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:339607394941801e6e3f6c1ecd413a36e18454e7136ed1161388de674f47f9d9", size = 5614973 }, + { url = "https://files.pythonhosted.org/packages/08/6f/e3df1c41adf27f4b8a95c9de947ed49e7311a676cd05bdd62a17bb1f21ec/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84819390a36d6166cec706b9d8f0941f115f700b7faecab5a7e22fc367408bc3", size = 1665667 }, + { url = "https://files.pythonhosted.org/packages/1a/9b/6c91b98dc70270c35913f359c17e30d4185c83663c4721363540f4c03016/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eea8d9e20632d68f653455265b18c35f90965e26f30d4d92f831899d6682149b", size = 1676166 }, + { url = "https://files.pythonhosted.org/packages/59/9d/eec7a1bfd3566fb17617b41bfb19556c483241d6864eea3c01b88efe5459/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b659e1e2ea2784a9a397075a7fc395bfa4fe66424042161c4bcaf6e4f637b38", size = 3130890 }, + { url = "https://files.pythonhosted.org/packages/26/7c/0a4bb5fbb03a362ea3e1409515d3ae641d9bc869c1375d97d8c47e369cc0/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1315cd2a351144572e31fe3df68340d4b83ddec0af8b2e207cd32930c6acd037", size = 2339850 }, + { url = "https://files.pythonhosted.org/packages/f8/c1/6b839db83caaa47721398b76390a3145202beb108fa433e842879b497439/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a7743cca45b4684c54407e8638f6d07b910d8d811347b9d42ff21262c7c23245", size = 6941921 }, + { url = "https://files.pythonhosted.org/packages/cc/c9/eaac43bb5e44f3594afddbbdb1a28d7bc0bcb69f93ed9a2ef0c949a48fb2/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5bb636b0150daa6d3331b738f7c0f8b25eadc47f04a40e5c23c4bfb4c4e20ae3", size = 2717551 }, + { url = "https://files.pythonhosted.org/packages/ef/d3/06ca5ee6b7f030f6527ea1e80fe9a4ab3597e86bc783574e3fc2b05a5265/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:42f4dd264ada7a9aa0805ea0da776dc063533917773cf2df5217f14eb4429eae", size = 3259550 }, + { url = "https://files.pythonhosted.org/packages/74/d8/094e75ee0424cce329901a0ff98c1821fd5d9dbc11bcdc9a3fddd2a09c4c/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51f24cb39e64256221e6952f22545b8ce21cacd59c0d3e367225da8fc4b868d8", size = 4173546 }, + { url = "https://files.pythonhosted.org/packages/d7/81/f263059e3d9f11b076751ac7ef4eba303fa7f11e32155658953f1697c274/rapidfuzz-3.11.0-cp311-cp311-win32.whl", hash = "sha256:aaf391fb6715866bc14681c76dc0308f46877f7c06f61d62cc993b79fc3c4a2a", size = 1842172 }, + { url = "https://files.pythonhosted.org/packages/33/04/dc42c787f02505a4ca0a961172e8353ceee74ea378b795f3e49686e944b7/rapidfuzz-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:ebadd5b8624d8ad503e505a99b8eb26fe3ea9f8e9c2234e805a27b269e585842", size = 1621122 }, + { url = "https://files.pythonhosted.org/packages/4e/0f/461e709bd641922a32bc034976963acbb11d8cf0af28b526f3f35ae07975/rapidfuzz-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:d895998fec712544c13cfe833890e0226585cf0391dd3948412441d5d68a2b8c", size = 864792 }, + { url = "https://files.pythonhosted.org/packages/c5/54/954ae2dc7dcb53f5f0953379a4a175d9c2f5e393656ab042843e53780d32/rapidfuzz-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f382fec4a7891d66fb7163c90754454030bb9200a13f82ee7860b6359f3f2fa8", size = 1938694 }, + { url = "https://files.pythonhosted.org/packages/f9/74/4682d3370821db5374c0f192d1e4123598190cb53d88936016187f80f154/rapidfuzz-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dfaefe08af2a928e72344c800dcbaf6508e86a4ed481e28355e8d4b6a6a5230e", size = 1423836 }, + { url = "https://files.pythonhosted.org/packages/e7/78/ce3d72767e186a9deca30dccb5096cfb03ec49e8e3abf2836ab10d1b4f74/rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92ebb7c12f682b5906ed98429f48a3dd80dd0f9721de30c97a01473d1a346576", size = 1393199 }, + { url = "https://files.pythonhosted.org/packages/3c/21/26bdbe846726ff7793789da07e155699cafa3ba3ed3bee86d472b4762121/rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a1b3ebc62d4bcdfdeba110944a25ab40916d5383c5e57e7c4a8dc0b6c17211a", size = 5543400 }, + { url = "https://files.pythonhosted.org/packages/c9/d5/78e922cfbfc67011ecee9f6c2fd630dee68650d23b9ce78316386a3d8c88/rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c6d7fea39cb33e71de86397d38bf7ff1a6273e40367f31d05761662ffda49e4", size = 1642855 }, + { url = "https://files.pythonhosted.org/packages/df/bb/dcf084c03c46968c3fbc52a33f2a725e0b8bb54ed714f0866c7dad747358/rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99aebef8268f2bc0b445b5640fd3312e080bd17efd3fbae4486b20ac00466308", size = 1669853 }, + { url = "https://files.pythonhosted.org/packages/ec/3a/9aa7a2c5b611e8d465e82c1d5f8278be7335769165f68f3ffc5a169f4a23/rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4469307f464ae3089acf3210b8fc279110d26d10f79e576f385a98f4429f7d97", size = 3129941 }, + { url = "https://files.pythonhosted.org/packages/d3/15/2bbab50a2634b25593e36241ab9629be253b8c6ea28a34ba6b856bfea661/rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:eb97c53112b593f89a90b4f6218635a9d1eea1d7f9521a3b7d24864228bbc0aa", size = 2302199 }, + { url = "https://files.pythonhosted.org/packages/c6/7c/e3ed92b89c657348c41708fe3b856ebc982c4b220b47299bdef8da374b20/rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef8937dae823b889c0273dfa0f0f6c46a3658ac0d851349c464d1b00e7ff4252", size = 6904702 }, + { url = "https://files.pythonhosted.org/packages/bd/4f/eed77097068bffb692d6389ae19a531c52a896275e9f5c00566207767537/rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d95f9e9f3777b96241d8a00d6377cc9c716981d828b5091082d0fe3a2924b43e", size = 2679287 }, + { url = "https://files.pythonhosted.org/packages/1f/dc/d2d5dcd5b33a5b394485c67aa13674c8345826af8d3ba0702c06ab2f6430/rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:b1d67d67f89e4e013a5295e7523bc34a7a96f2dba5dd812c7c8cb65d113cbf28", size = 3224946 }, + { url = "https://files.pythonhosted.org/packages/8f/af/17c0c29ded64e464e626dd43fc2e3028c1fa929d10e8201fb2aec654e5b3/rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d994cf27e2f874069884d9bddf0864f9b90ad201fcc9cb2f5b82bacc17c8d5f2", size = 4144678 }, + { url = "https://files.pythonhosted.org/packages/66/5d/5dc02c87d9a0e64e0abd728d3255ddce8475e06b6be3f732a460f0a360c9/rapidfuzz-3.11.0-cp312-cp312-win32.whl", hash = "sha256:ba26d87fe7fcb56c4a53b549a9e0e9143f6b0df56d35fe6ad800c902447acd5b", size = 1824882 }, + { url = "https://files.pythonhosted.org/packages/b7/da/a37d532cbefd7242191abf18f438b315bf5c72d742f78414a8ec1b7396cf/rapidfuzz-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1f7efdd7b7adb32102c2fa481ad6f11923e2deb191f651274be559d56fc913b", size = 1606419 }, + { url = "https://files.pythonhosted.org/packages/92/d0/1406d6e110aff87303e98f47adc5e76ef2e69d51cdd08b2d463520158cab/rapidfuzz-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:ed78c8e94f57b44292c1a0350f580e18d3a3c5c0800e253f1583580c1b417ad2", size = 858655 }, + { url = "https://files.pythonhosted.org/packages/8a/30/984f1013d28b88304386c8e70b5d63db4765c28be8d9ef68d177c9addc77/rapidfuzz-3.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e60814edd0c9b511b5f377d48b9782b88cfe8be07a98f99973669299c8bb318a", size = 1931354 }, + { url = "https://files.pythonhosted.org/packages/a4/8a/41d4f95c5742a8a47c0e96c02957f72f8c34411cecde87fe371d5e09807e/rapidfuzz-3.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f28952da055dbfe75828891cd3c9abf0984edc8640573c18b48c14c68ca5e06", size = 1417918 }, + { url = "https://files.pythonhosted.org/packages/e3/26/031ac8366831da6afc5f25462196eab0e0caf9422c83c007307e23a6f010/rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e8f93bc736020351a6f8e71666e1f486bb8bd5ce8112c443a30c77bfde0eb68", size = 1388327 }, + { url = "https://files.pythonhosted.org/packages/17/1b/927edcd3b540770d3d6d52fe079c6bffdb99e9dfa4b73585bee2a8bd6504/rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76a4a11ba8f678c9e5876a7d465ab86def047a4fcc043617578368755d63a1bc", size = 5513214 }, + { url = "https://files.pythonhosted.org/packages/0d/a2/c1e4f35e7bfbbd97a665f8cd119d8bd4a085f1721366cd76582dc022131b/rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc0e0d41ad8a056a9886bac91ff9d9978e54a244deb61c2972cc76b66752de9c", size = 1638560 }, + { url = "https://files.pythonhosted.org/packages/39/3f/6827972efddb1e357a0b6165ae9e310d7dc5c078af3023893365c212641b/rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e8ea35f2419c7d56b3e75fbde2698766daedb374f20eea28ac9b1f668ef4f74", size = 1667185 }, + { url = "https://files.pythonhosted.org/packages/cc/5d/6902b93e1273e69ea087afd16e7504099bcb8d712a9f69cb649ea05ca7e1/rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd340bbd025302276b5aa221dccfe43040c7babfc32f107c36ad783f2ffd8775", size = 3107466 }, + { url = "https://files.pythonhosted.org/packages/a6/02/bdb2048c9b8edf4cd82c2e8f6a8ed9af0fbdf91810ca2b36d1be6fc996d8/rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:494eef2c68305ab75139034ea25328a04a548d297712d9cf887bf27c158c388b", size = 2302041 }, + { url = "https://files.pythonhosted.org/packages/12/91/0bbe51e3c15c02578487fd10a14692a40677ea974098d8d376bafd627a89/rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5a167344c1d6db06915fb0225592afdc24d8bafaaf02de07d4788ddd37f4bc2f", size = 6899969 }, + { url = "https://files.pythonhosted.org/packages/27/9d/09b85adfd5829f60bd6dbe53ba66dad22f93a281d494a5638b5f20fb6a8a/rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8c7af25bda96ac799378ac8aba54a8ece732835c7b74cfc201b688a87ed11152", size = 2669022 }, + { url = "https://files.pythonhosted.org/packages/cb/07/6fb723963243335c3bf73925914b6998649d642eff550187454d5bb3d077/rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d2a0f7e17f33e7890257367a1662b05fecaf56625f7dbb6446227aaa2b86448b", size = 3229475 }, + { url = "https://files.pythonhosted.org/packages/3a/8e/e9af6da2e235aa29ad2bb0a1fc2472b2949ed8d9ff8fb0f05b4bfbbf7675/rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d0d26c7172bdb64f86ee0765c5b26ea1dc45c52389175888ec073b9b28f4305", size = 4143861 }, + { url = "https://files.pythonhosted.org/packages/fd/d8/4677e36e958b4d95d039d254d597db9c020896c8130911dc36b136373b87/rapidfuzz-3.11.0-cp313-cp313-win32.whl", hash = "sha256:6ad02bab756751c90fa27f3069d7b12146613061341459abf55f8190d899649f", size = 1822624 }, + { url = "https://files.pythonhosted.org/packages/e8/97/1c782140e688ea2c3337d94516c635c575aa39fe62782fd53ad5d2119df4/rapidfuzz-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:b1472986fd9c5d318399a01a0881f4a0bf4950264131bb8e2deba9df6d8c362b", size = 1604273 }, + { url = "https://files.pythonhosted.org/packages/a6/83/8b713d50bec947e945a79be47f772484307fc876c426fb26c6f369098389/rapidfuzz-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c408f09649cbff8da76f8d3ad878b64ba7f7abdad1471efb293d2c075e80c822", size = 857385 }, ] [[package]] @@ -1223,7 +1253,7 @@ requires-dist = [ { name = "pendulum", specifier = ">=3.0.0" }, { name = "pillow", specifier = ">=11.1.0" }, { name = "psutil", specifier = ">=5.9.4" }, - { name = "rapidfuzz", specifier = "==2.13.7" }, + { name = "rapidfuzz", specifier = "==3.11.0" }, { name = "requests", specifier = ">=2.27.1" }, { name = "schedule", specifier = ">=1.2.2" }, { name = "setproctitle", specifier = ">=1.3.2" },