diff --git a/package.json b/package.json index 14d6259b..0861b036 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,10 @@ "lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src" }, "dependencies": { + "@vueuse/core": "^8.5.0", + "@vueuse/motion": "^2.0.0-beta.18", "axios": "^0.26.1", + "defu": "^6.0.0", "mitt": "^3.0.0", "node-vibrant": "^3.2.1-alpha.1", "pinia": "^2.0.11", diff --git a/server/app/api/search.py b/server/app/api/search.py index 18da8342..9c550cec 100644 --- a/server/app/api/search.py +++ b/server/app/api/search.py @@ -1,11 +1,10 @@ """ Contains all the search routes. """ - -from flask import Blueprint, request - -from app.lib import searchlib from app import helpers +from app.lib import searchlib +from flask import Blueprint +from flask import request search_bp = Blueprint("search", __name__, url_prefix="/") @@ -16,6 +15,63 @@ SEARCH_RESULTS = { } +@search_bp.route("/search/tracks", methods=["GET"]) +def search_tracks(): + """ + Searches for tracks. + """ + + query = request.args.get("q") + if not query: + return {"error": "No query provided"}, 400 + + results = searchlib.SearchTracks(query)() + SEARCH_RESULTS["tracks"] = results + + return { + "tracks": results[:5], + "more": len(results) > 5, + }, 200 + + +@search_bp.route("/search/albums", methods=["GET"]) +def search_albums(): + """ + Searches for albums. + """ + + query = request.args.get("q") + if not query: + return {"error": "No query provided"}, 400 + + results = searchlib.SearchAlbums(query)() + SEARCH_RESULTS["albums"] = results + + return { + "albums": results[:6], + "more": len(results) > 6, + }, 200 + + +@search_bp.route("/search/artists", methods=["GET"]) +def search_artists(): + """ + Searches for artists. + """ + + query = request.args.get("q") + if not query: + return {"error": "No query provided"}, 400 + + results = searchlib.SearchArtists(query)() + SEARCH_RESULTS["artists"] = results + + return { + "artists": results[:6], + "more": len(results) > 6, + }, 200 + + @search_bp.route("/search") def search(): """ @@ -23,25 +79,14 @@ def search(): """ query = request.args.get("q") or "Mexican girl" - albums = searchlib.get_search_albums(query) - artists_dicts = [] + albums = searchlib.SearchAlbums(query)() + artists_dicts = searchlib.SearchArtists(query)() - artist_tracks = searchlib.get_artists(query) + tracks = searchlib.SearchTracks(query)() + top_artist = artists_dicts[0]["name"] - for song in artist_tracks: - for artist in song.artists: - if query.lower() in artist.lower(): - - artist_obj = { - "name": artist, - "image": helpers.check_artist_image(artist), - } - - if artist_obj not in artists_dicts: - artists_dicts.append(artist_obj) - - _tracks = searchlib.get_tracks(query) - tracks = [*_tracks, *artist_tracks] + _tracks = searchlib.GetTopArtistTracks(top_artist)() + tracks = [*tracks, *[t for t in _tracks if t not in tracks]] SEARCH_RESULTS.clear() SEARCH_RESULTS["tracks"] = tracks @@ -50,9 +95,18 @@ def search(): return { "data": [ - {"tracks": tracks[:5], "more": len(tracks) > 5}, - {"albums": albums[:6], "more": len(albums) > 6}, - {"artists": artists_dicts[:6], "more": len(artists_dicts) > 6}, + { + "tracks": tracks[:5], + "more": len(tracks) > 5 + }, + { + "albums": albums[:6], + "more": len(albums) > 6 + }, + { + "artists": artists_dicts[:6], + "more": len(artists_dicts) > 6 + }, ] } @@ -63,22 +117,25 @@ def search_load_more(): Returns more songs, albums or artists from a search query. """ type = request.args.get("type") - start = int(request.args.get("start")) + index = int(request.args.get("index")) + + print(type, index) + print(len(SEARCH_RESULTS["tracks"])) if type == "tracks": return { - "tracks": SEARCH_RESULTS["tracks"][start : start + 5], - "more": len(SEARCH_RESULTS["tracks"]) > start + 5, + "tracks": SEARCH_RESULTS["tracks"][index:index + 5], + "more": len(SEARCH_RESULTS["tracks"]) > index + 5, } elif type == "albums": return { - "albums": SEARCH_RESULTS["albums"][start : start + 6], - "more": len(SEARCH_RESULTS["albums"]) > start + 6, + "albums": SEARCH_RESULTS["albums"][index:index + 6], + "more": len(SEARCH_RESULTS["albums"]) > index + 6, } elif type == "artists": return { - "artists": SEARCH_RESULTS["artists"][start : start + 6], - "more": len(SEARCH_RESULTS["artists"]) > start + 6, + "artists": SEARCH_RESULTS["artists"][index:index + 6], + "more": len(SEARCH_RESULTS["artists"]) > index + 6, } diff --git a/server/app/helpers.py b/server/app/helpers.py index e6278fee..3cf49327 100644 --- a/server/app/helpers.py +++ b/server/app/helpers.py @@ -1,11 +1,11 @@ """ This module contains mini functions for the server. """ -from datetime import datetime import os import random import threading import time +from datetime import datetime from typing import Dict from typing import List @@ -27,7 +27,9 @@ def background(func): return background_func -def run_fast_scandir(__dir: str, ext: list, full=False) -> Dict[List[str], List[str]]: +def run_fast_scandir(__dir: str, + ext: list, + full=False) -> Dict[List[str], List[str]]: """ Scans a directory for files with a specific extension. Returns a list of files and folders in the directory. """ @@ -60,12 +62,10 @@ def remove_duplicates(tracklist: List[models.Track]) -> List[models.Track]: while song_num < len(tracklist) - 1: for index, song in enumerate(tracklist): - if ( - tracklist[song_num].title == song.title - and tracklist[song_num].album == song.album - and tracklist[song_num].artists == song.artists - and index != song_num - ): + if (tracklist[song_num].title == song.title + and tracklist[song_num].album == song.album + and tracklist[song_num].artists == song.artists + and index != song_num): tracklist.remove(song) song_num += 1 @@ -108,7 +108,8 @@ def check_artist_image(image: str) -> str: """ img_name = image.replace("/", "::") + ".webp" - if not os.path.exists(os.path.join(app_dir, "images", "artists", img_name)): + if not os.path.exists(os.path.join(app_dir, "images", "artists", + img_name)): return use_memoji() else: return img_name @@ -120,7 +121,15 @@ def create_album_hash(title: str, artist: str) -> str: """ return (title + artist).replace(" ", "").lower() + def create_new_date(): now = datetime.now() str = now.strftime("%Y-%m-%d %H:%M:%S") - return str \ No newline at end of file + return str + + +def create_safe_name(name: str) -> str: + """ + Creates a url-safe name from a name. + """ + return "".join([i for i in name if i not in '/\\:*?"<>|']) diff --git a/server/app/lib/albumslib.py b/server/app/lib/albumslib.py index 67193893..58c84669 100644 --- a/server/app/lib/albumslib.py +++ b/server/app/lib/albumslib.py @@ -6,13 +6,13 @@ import urllib from typing import List from app import api +from app import helpers from app import instances from app import models from app.lib import taglib from app.lib import trackslib from progress.bar import Bar from tqdm import tqdm -from app import helpers def get_all_albums() -> List[models.Album]: @@ -52,6 +52,10 @@ def create_everything() -> List[models.Track]: def find_album(albums: List[models.Album], hash: str) -> int | None: """ Finds an album by album title and artist. + + :param `albums`: List of album objects. + :param `hash`: Hash of album. + :return: Index of album in list. """ left = 0 @@ -62,8 +66,11 @@ def find_album(albums: List[models.Album], hash: str) -> int | None: iter += 1 mid = (left + right) // 2 - if albums[mid].hash == hash: - return mid + try: + if albums[mid].hash == hash: + return mid + except AttributeError: + print(albums) if albums[mid].hash < hash: left = mid + 1 @@ -151,7 +158,7 @@ def get_album_tracks(album: str, artist: str) -> List: return GetAlbumTracks(album, artist).find_tracks() -def create_album(track: dict, tracklist: list) -> models.Album: +def create_album(track: dict, tracklist: list) -> dict: """ Generates and returns an album object from a track object. """ diff --git a/server/app/lib/populate.py b/server/app/lib/populate.py index df4c0be3..c5792257 100644 --- a/server/app/lib/populate.py +++ b/server/app/lib/populate.py @@ -1,25 +1,26 @@ +import os +import time from concurrent.futures import ThreadPoolExecutor from copy import deepcopy - +from multiprocessing import Pool from os import path -import time from typing import List -from tqdm import tqdm - from app import api from app import settings -from app.helpers import create_album_hash, run_fast_scandir +from app.helpers import create_album_hash +from app.helpers import run_fast_scandir from app.instances import album_instance from app.instances import tracks_instance from app.lib import folderslib from app.lib.albumslib import create_album from app.lib.albumslib import find_album from app.lib.taglib import get_tags -from app.logger import Log -from app.models import Album, Track - from app.lib.trackslib import find_track +from app.logger import Log +from app.models import Album +from app.models import Track +from tqdm import tqdm class Populate: @@ -46,7 +47,7 @@ class Populate: def run(self): self.check_untagged() - self.tag_all_files() + self.get_all_tags() if len(self.tagged_tracks) == 0: return @@ -76,6 +77,17 @@ class Populate: Log(f"Found {len(self.files)} untagged tracks") + def process_tags(self, tags: dict): + for t in tags: + if t is None: + continue + + t["albumhash"] = create_album_hash(t["album"], t["albumartist"]) + self.tagged_tracks.append(t) + api.DB_TRACKS.append(t) + + self.folders.add(t["folder"]) + def get_tags(self, file: str): tags = get_tags(file) @@ -83,19 +95,27 @@ class Populate: folder = tags["folder"] self.folders.add(folder) - tags["albumhash"] = create_album_hash(tags["album"], tags["albumartist"]) + tags["albumhash"] = create_album_hash(tags["album"], + tags["albumartist"]) self.tagged_tracks.append(tags) api.DB_TRACKS.append(tags) - def tag_all_files(self): + def get_all_tags(self): """ Loops through all the untagged files and tags them. """ s = time.time() - print(f"Started tagging files") - with ThreadPoolExecutor() as executor: - executor.map(self.get_tags, self.files) + # print(f"Started tagging files") + # with ThreadPoolExecutor() as executor: + # executor.map(self.get_tags, self.files) + + with Pool(maxtasksperchild=10) as p: + tags = p.map(get_tags, tqdm(self.files)) + self.process_tags(tags) + + # for t in tqdm(self.files): + # self.get_tags(t) d = time.time() - s Log(f"Tagged {len(self.tagged_tracks)} files in {d} seconds") @@ -149,9 +169,8 @@ class Populate: for album in tqdm(self.pre_albums, desc="Building albums"): self.create_album(album) - Log( - f"{self.exist_count} of {len(self.pre_albums)} albums were already in the database" - ) + Log(f"{self.exist_count} of {len(self.pre_albums)} albums were already in the database" + ) def create_track(self, track: dict): """ @@ -186,9 +205,8 @@ class Populate: with ThreadPoolExecutor() as executor: executor.map(self.create_track, self.tagged_tracks) - Log( - f"Added {len(self.tagged_tracks)} new tracks and {len(self.albums)} new albums" - ) + Log(f"Added {len(self.tagged_tracks)} new tracks and {len(self.albums)} new albums" + ) def save_albums(self): """ diff --git a/server/app/lib/searchlib.py b/server/app/lib/searchlib.py index ae7225c2..6b8ff223 100644 --- a/server/app/lib/searchlib.py +++ b/server/app/lib/searchlib.py @@ -1,19 +1,156 @@ """ This library contains all the functions related to the search functionality. """ - from typing import List -from app import models, helpers -from app.lib import albumslib + from app import api +from app import helpers +from app import models +from app.lib import albumslib +from rapidfuzz import fuzz +from rapidfuzz import process + +ratio = fuzz.ratio +wratio = fuzz.WRatio -def get_tracks(query: str) -> List[models.Track]: +class Cutoff: """ - Gets all songs with a given title. + Holds all the default cutoff values. """ - tracks = [track for track in api.TRACKS if query.lower() in track.title.lower()] - return helpers.remove_duplicates(tracks) + + tracks: int = 70 + albums: int = 70 + artists: int = 70 + + +class Limit: + """ + Holds all the default limit values. + """ + + tracks: int = 50 + albums: int = 50 + artists: int = 50 + + +class SearchTracks: + + def __init__(self, query) -> None: + self.query = query + + def __call__(self) -> List[models.Track]: + """ + Gets all songs with a given title. + """ + + tracks = [track.title for track in api.TRACKS] + results = process.extract( + self.query, + tracks, + scorer=fuzz.WRatio, + score_cutoff=Cutoff.tracks, + limit=Limit.tracks, + ) + + return [api.TRACKS[i[2]] for i in results] + + +class SearchArtists: + + def __init__(self, query) -> None: + self.query = query + + @staticmethod + def get_all_artist_names() -> List[str]: + """ + Gets all artist names. + """ + + artists = [track.artists for track in api.TRACKS] + + f_artists = set() + + for artist in artists: + for a in artist: + f_artists.add(a) + + return f_artists + + def __call__(self) -> list: + """ + Gets all artists with a given name. + """ + + artists = self.get_all_artist_names() + results = process.extract( + self.query, + artists, + scorer=fuzz.WRatio, + score_cutoff=Cutoff.artists, + limit=Limit.artists, + ) + + f_artists = [] + for artist in results: + aa = { + "name": artist[0], + "image": helpers.create_safe_name(artist[0]) + ".webp", + } + f_artists.append(aa) + + return f_artists + + +class SearchAlbums: + + def __init__(self, query) -> None: + self.query = query + + def get_albums_by_name(self) -> List[models.Album]: + """ + Gets all albums with a given title. + """ + + albums = [album.title for album in api.ALBUMS] + results = process.extract(self.query, albums) + return [api.ALBUMS[i[2]] for i in results] + + def __call__(self) -> List[models.Album]: + """ + Gets all albums with a given title. + """ + + albums = [a.title for a in api.ALBUMS] + results = process.extract( + self.query, + albums, + scorer=fuzz.WRatio, + score_cutoff=Cutoff.albums, + limit=Limit.albums, + ) + + return [api.ALBUMS[i[2]] for i in results] + + # get all artists that matched the query + # for get all albums from the artists + # get all albums that matched the query + # return [**artist_albums **albums] + + # recheck next and previous artist on play next or add to playlist + + +class GetTopArtistTracks: + + def __init__(self, artist: str) -> None: + self.artist = artist + + def __call__(self) -> List[models.Track]: + """ + Gets all tracks from a given artist. + """ + + return [track for track in api.TRACKS if self.artist in track.artists] def get_search_albums(query: str) -> List[models.Album]: @@ -27,4 +164,7 @@ def get_artists(artist: str) -> List[models.Track]: """ Gets all songs with a given artist. """ - return [track for track in api.TRACKS if artist.lower() in str(track.artists).lower()] + return [ + track for track in api.TRACKS + if artist.lower() in str(track.artists).lower() + ] diff --git a/server/app/lib/taglib.py b/server/app/lib/taglib.py index 3a695a51..bee344ff 100644 --- a/server/app/lib/taglib.py +++ b/server/app/lib/taglib.py @@ -154,7 +154,7 @@ def parse_disk_number(audio): return disk_number -def get_tags(fullpath: str) -> dict: +def get_tags(fullpath: str) -> dict | None: """ Returns a dictionary of tags for a given file. """ diff --git a/server/app/lib/watchdoge.py b/server/app/lib/watchdoge.py index e094d849..91b34196 100644 --- a/server/app/lib/watchdoge.py +++ b/server/app/lib/watchdoge.py @@ -7,14 +7,14 @@ import time from app import api from app import instances from app import models +from app.helpers import create_album_hash from app.lib import folderslib -from app.lib.albumslib import create_album, find_album +from app.lib.albumslib import create_album +from app.lib.albumslib import find_album from app.lib.taglib import get_tags from watchdog.events import PatternMatchingEventHandler from watchdog.observers import Observer -from app.helpers import create_album_hash - class OnMyWatch: """ @@ -50,18 +50,19 @@ def add_track(filepath: str) -> None: tags = get_tags(filepath) if tags is not None: - tags["albumhash"] = create_album_hash(tags["album"], tags["albumartist"]) + hash = create_album_hash(tags["album"], tags["albumartist"]) + tags["albumhash"] = hash api.DB_TRACKS.append(tags) - albumindex = find_album(tags["album"], tags["albumartist"]) + albumindex = find_album(api.ALBUMS, hash) if albumindex is not None: album = api.ALBUMS[albumindex] else: album_data = create_album(tags, api.DB_TRACKS) - instances.album_instance.insert_album(album_data) - album = models.Album(album_data) + + instances.album_instance.insert_album(album) api.ALBUMS.append(album) tags["image"] = album.image @@ -86,7 +87,8 @@ def remove_track(filepath: str) -> None: fpath = filepath.replace(fname, "") try: - trackid = instances.tracks_instance.get_song_by_path(filepath)["_id"]["$oid"] + trackid = instances.tracks_instance.get_song_by_path( + filepath)["_id"]["$oid"] except TypeError: print(f"💙 Watchdog Error: Error removing track {filepath} TypeError") return @@ -152,7 +154,14 @@ class Handler(PatternMatchingEventHandler): Fired when a created file is closed. """ print("⚫ closed ~~~") - self.files_to_process.remove(event.src_path) + try: + self.files_to_process.remove(event.src_path) + except ValueError: + """ + The file was already removed from the list, or it was not in the list to begin with. + """ + pass + add_track(event.src_path) diff --git a/server/app/models.py b/server/app/models.py index d3d3a35f..27453ff1 100644 --- a/server/app/models.py +++ b/server/app/models.py @@ -6,8 +6,8 @@ from dataclasses import field from typing import List from app import api -from app.exceptions import TrackExistsInPlaylist from app import helpers +from app.exceptions import TrackExistsInPlaylist @dataclass(slots=True) @@ -18,7 +18,7 @@ class Track: trackid: str title: str - artists: str + artists: list albumartist: str album: str folder: str @@ -32,8 +32,11 @@ class Track: albumhash: str def __init__(self, tags): + try: + self.trackid = tags["_id"]["$oid"] + except KeyError: + print(tags) - self.trackid = tags["_id"]["$oid"] self.title = tags["title"] self.artists = tags["artists"].split(", ") self.albumartist = tags["albumartist"] @@ -49,6 +52,22 @@ class Track: self.albumhash = tags["albumhash"] +@dataclass(slots=True) +class Artist: + """ + Artist class + """ + + artistid: str + name: str + image: str + + def __init__(self, tags): + self.artistid = tags["_id"]["$oid"] + self.name = tags["name"] + self.image = tags["image"] + + @dataclass class Album: """ @@ -78,11 +97,9 @@ class Album: def get_p_track(ptrack): for track in api.TRACKS: - if ( - track.title == ptrack["title"] - and track.artists == ptrack["artists"] - and ptrack["album"] == track.album - ): + if (track.title == ptrack["title"] + and track.artists == ptrack["artists"] + and ptrack["album"] == track.album): return track diff --git a/server/poetry.lock b/server/poetry.lock index e20a8fe5..92ba5c7c 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -118,6 +118,14 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "jarowinkler" +version = "1.0.2" +description = "library for fast approximate string matching using Jaro and Jaro-Winkler similarity" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "jinja2" version = "3.0.3" @@ -181,6 +189,20 @@ snappy = ["python-snappy"] srv = ["dnspython (>=1.16.0,<3.0.0)"] zstd = ["zstandard"] +[[package]] +name = "rapidfuzz" +version = "2.0.11" +description = "rapid fuzzy string matching" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +jarowinkler = ">=1.0.2,<1.1.0" + +[package.extras] +full = ["numpy"] + [[package]] name = "requests" version = "2.27.1" @@ -262,7 +284,7 @@ watchdog = ["watchdog"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "a2a0355e78fe2881e226dabda21b6d972a98aa4f1d60bf9f7f74957bb8ba6bea" +content-hash = "143a7a7f2158b3c3abfcfec207443a90f1a3d31a6935dfdf5e5e7452e226ca58" [metadata.files] certifi = [ @@ -309,6 +331,88 @@ itsdangerous = [ {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"}, {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, ] +jarowinkler = [ + {file = "jarowinkler-1.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:71772fcd787e0286b779de0f1bef1e0a25deb4578328c0fc633bc345f13ffd20"}, + {file = "jarowinkler-1.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:912ee0a465822a8d659413cebc1ab9937ac5850c9cd1e80be478ba209e7c8095"}, + {file = "jarowinkler-1.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0320f7187dced1ad413bf2c3631ec47567e65dfdea92c523aafb2c085ae15035"}, + {file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58bc6a8f01b0dfdf3721f9a4954060addeccf8bbe5e72a71cf23a88ce0d30440"}, + {file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:679ec7a42f70baa61f3a214d1b59cec90fc036021c759722075efcc8697e7b1f"}, + {file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dde57d47962d6a4436d8a3b477bcc8233c6da28e675027eb3a490b0d6dc325be"}, + {file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:657f50204970fac8f120c293e52a3451b742c9b26125010405ec7365cb6e2a49"}, + {file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04f18a7398766b36ffbe4bcd26d34fcd6ed01f4f2f7eea13e316e6cca0e10c98"}, + {file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33a24b380e2c076eabf2d3e12eee56b6bf10b1f326444e18c36a495387dbf0de"}, + {file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e1d7d6e6c98fb785026584373240cc4076ad21033f508973faae05e846206e8c"}, + {file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e50c750a45c800d91134200d8cbf746258ed357a663e97cc0348ee42a948386a"}, + {file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:5b380afce6cdc25a4dafd86874f07a393800577c05335c6ad67ccda41db95c60"}, + {file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e73712747ac5d2218af3ed3c1600377f18a0a45af95f22c39576165aea2908b4"}, + {file = "jarowinkler-1.0.2-cp310-cp310-win32.whl", hash = "sha256:9511f4e1f00c822e08dbffeb69e15c75eb294a5f24729815a97807ecf03d22eb"}, + {file = "jarowinkler-1.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a5c44f92e9ac6088286292ecb69e970adc2b98e139b8923bce9bbb9d484e6a0f"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:02b0bf34ffc2995b695d9b10d2f18c1c447fbbdb7c913a84a0a48c186ccca3b8"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7a8e45176298a1210c06f8b2328030cc3c93a45dab068ac1fbc9cf075cd95b"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da27a9c206249a50701bfa5cfbbb3a04236e1145b2b0967e825438acb14269bf"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43ea0155379df92021af0f4a32253be3953dfa0f050ec3515f314b8f48a96674"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f33b6b1687db1be1abba60850628ee71547501592fcf3504e021274bc5ccb7a"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff304de32ee6acd5387103a0ad584060d8d419aa19cbbeca95204de9c4f01171"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:662dd6f59cca536640be0cda32c901989504d95316b192e6aa41d098fa08c795"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:01f85abb75fa43e98db34853d35570d98495ee2fcbbf45a93838e0289c162f19"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5b9332dcc8130af4101c9752a03e977c54b8c12982a2a3ca4c2e4cc542accc00"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:af765b037404a536c372e33ddd4c430aea28f1d82a8ef51a2955442b8b690577"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aea2c7d66b57c56d00f9c45ae7862d86e3ae84368ecea17f3552c0052a7f3bcf"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-win32.whl", hash = "sha256:8b1288a09a8d100e9bf7cf9ce1329433db73a0d0350d74c2c6f5c31ac69096cf"}, + {file = "jarowinkler-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ed39199b0e806902347473c65e5c05933549cf7e55ba628c6812782f2c310b19"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:473b057d7e5a0f5e5b8c0e0f7960d3ca2f2954c3c93fd7a9fb2cc4bc3cc940fb"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdb892dbbbd77b3789a10b2ce5e8acfe5821cc6423e835bae2b489159f3c2211"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:012a8333328ce061cba1ff081843c8d80eb1afe8fa2889ad29d767ea3fdc7562"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3421120c07ee6d3f59c5adde32eb9a050cfd1b3666b0e2d8c337d934a9d091f9"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dad57327cc90f8daa3afb98e2d274d7dd1b60651f32717449be95d3b3366d61a"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4fd1757eff43df97227fd63d9c8078582267a0b25cefef6f6a64d3e46e80ba2"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:32269ebbcb860f01c055d9bb145b4cc91990f62c7644a85b21458b4868621113"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3b5a0839e84f5ff914b01b5b94d0273954affce9cc2b2ee2c31fe2fcb9c8ae76"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:6c9d3a9ef008428b5dce2855eebe2b6127ea7a7e433aedf240653fad4bd4baa6"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3d7759d8a66ee05595bde012f93da8a63499f38205e2bb47022c52bd6c47108"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2ba1b1b0bf45042a9bbb95d272fd8b0c559fe8f6806f088ec0372899e1bc6224"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-win32.whl", hash = "sha256:4cb33f4343774d69abf8cf65ad57919e7a171c44ba6ad57b08147c3f0f06b073"}, + {file = "jarowinkler-1.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:0392b72ddb5ab5d6c1d5df94dbdac7bf229670e5e64b2b9a382d02d6158755e5"}, + {file = "jarowinkler-1.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:94f663ad85bc7a89d7e8b6048f93a46d2848a0570ab07fc895a239b9a5d97b93"}, + {file = "jarowinkler-1.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:895a10766ff3db15e7cf2b735e4277bee051eaafb437aaaef2c5de64a5c3f05c"}, + {file = "jarowinkler-1.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0c1a84e770b3ec7385a4f40efb30bdc96f96844564f91f8d3937d54a8969d82c"}, + {file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27defe81d76e02b3929322baea999f5232837e7f308c2dc5b37de7568c2bc583"}, + {file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:158f117481388f8d23fe4bd2567f37be0ccae0f4631c34e4b0345803147da207"}, + {file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:427c675b4f3e83c79a4b6af7441f29e30a173c7a0ae72a54f51090eee7a8ae02"}, + {file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90a7f3fd173339bc62e52c02f43d50c947cb3af9cda41646e218aea13547e0c2"}, + {file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3975cbe8b6ae13fc63d74bcbed8dac1577078d8cd8728e60621fe75885d2a8c5"}, + {file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:141840f33345b00abd611839080edc99d4d31abd2dcf701a3e50c90f9bfb2383"}, + {file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f592f9f6179e347a5f518ca7feb9bf3ac068f2fad60ece5a0eef5e5e580d4c8b"}, + {file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:30565d70396eb9d1eb622e1e707ddc2f3b7a9692558b8bf4ea49415a5ca2f854"}, + {file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:35fc430c11b80a43ed826879c78c4197ec665d5150745b3668bec961acf8a757"}, + {file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf4b7090f0c4075bec1638717f54b22c3b0fe733dc87146a19574346ed3161"}, + {file = "jarowinkler-1.0.2-cp38-cp38-win32.whl", hash = "sha256:199f4f7edbc49439a97440caa1e244d2e33da3e16d7b0afce4e4dfd307e555c7"}, + {file = "jarowinkler-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:b587e8fdd96cc470d6bdf428129c65264731b09b5db442e2d092e983feec4aab"}, + {file = "jarowinkler-1.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4b233180b3e2f2d7967aa570d36984e9d2ec5a9067c0d1c44cd3b805d9da9363"}, + {file = "jarowinkler-1.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2220665a1f52262ae8b76e3baf474ebcd209bfcb6a7cada346ffd62818f5aa3e"}, + {file = "jarowinkler-1.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08c98387e04e749c84cc967db628e5047843f19f87bf515a35b72f7050bc28ad"}, + {file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d710921657442ad3c942de684aba0bdf16b7de5feed3223b12f3b2517cf17f7c"}, + {file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:401c02ac7245103826f54c816324274f53d50b638ab0f8b359a13055a7a6e793"}, + {file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a1929a0029f208cc9244499dc93b4d52ee8e80d2849177d425cf6e0be1ea781"}, + {file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab25d147be9b04e7de2d28a18e72fadc152698c3e51683c6c61f73ffbae2f9e"}, + {file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:465cfdff355ec9c55f65fd1e1315260ec20c8cff0eb90d9f1a0ad8d503dc002b"}, + {file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:29ef1113697cc74c2f04bc15008abbd726cb2d5b01c040ba87c6cb7abd1d0e0d"}, + {file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:61b57c8b36361ec889f99f761441bb0fa21b850a5eb3305dea25fef68f6a797b"}, + {file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ee9d9af1bbf194d78f4b69c2139807c23451068b27a053a1400d683d6f36c61d"}, + {file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a9b33b0ceb472bbc65683467189bd032c162256b2a137586ee3448a9f8f886ec"}, + {file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:582f6e213a6744883ced44482a51efcc21ae632defac27f12f6430a8e99b1070"}, + {file = "jarowinkler-1.0.2-cp39-cp39-win32.whl", hash = "sha256:4d1c8f403016d5c0262de7a8588eee370c37a609e1f529f8407e99a70d020af7"}, + {file = "jarowinkler-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:ab50ffa66aa201616871c1b90ac0790f56666118db3c8a8fcb3a7a6e03971510"}, + {file = "jarowinkler-1.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8e59a289dcf93504ab92795666c39b2dbe98ac18655201992a7e6247de676bf4"}, + {file = "jarowinkler-1.0.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c36eccdc866f06a7b35da701bd8f91e0dfc83b35c07aba75ce8c906cbafaf184"}, + {file = "jarowinkler-1.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123163f01a5c43f12e4294e7ce567607d859e1446b1a43bd6cd404b3403ffa07"}, + {file = "jarowinkler-1.0.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d41fdecd907189e47c7d478e558ad417da38bf3eb34cc20527035cb3fca3e2b8"}, + {file = "jarowinkler-1.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7829368fc91de225f37f6325f8d8ec7ad831dc5b0e9547f1977e2fdc85eccc1"}, + {file = "jarowinkler-1.0.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278595417974553a8fdf3c8cce5c2b4f859335344075b870ecb55cc416eb76cf"}, + {file = "jarowinkler-1.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:208fc49741db5d3e6bbd4a2f7b32d32644b462bf205e7510eca4e2d530225f03"}, + {file = "jarowinkler-1.0.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:924afcab6739c453f1c3492701d185d71dc0e5ba15692bd0bfa6d482c7e8f79e"}, + {file = "jarowinkler-1.0.2.tar.gz", hash = "sha256:788ac33e6ffdbd78fd913b481e37cfa149288575f087a1aae1a4ce219cb1c654"}, +] jinja2 = [ {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, @@ -513,6 +617,54 @@ pymongo = [ {file = "pymongo-4.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:65f8a93816dcb2202710839907759aca9eece94d9f13215686f224fcc8966f9e"}, {file = "pymongo-4.0.1.tar.gz", hash = "sha256:13d0624c13a91da71fa0d960205d93b3d98344481be865ee7cc238c972d41d73"}, ] +rapidfuzz = [ + {file = "rapidfuzz-2.0.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb54edd0fa8620d37a7c0762895260bc75a6cc083d161b14d40a562b6f303975"}, + {file = "rapidfuzz-2.0.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8093b5f234be618bb8cfe34d65c072fee362fbd13f6c1b37f80eac0f30c24cfa"}, + {file = "rapidfuzz-2.0.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea72586c08ba8ce08c37c21bb7c383df740dc7d6e921423e1881570be62ed15"}, + {file = "rapidfuzz-2.0.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ee9a057b7638e91377b217df3724d4adefec3936617180b3df1f64fa64cd995"}, + {file = "rapidfuzz-2.0.11-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bad453a76f6832a99251beb89c352a4f436f4e7687a5843b080c294dba68d8d6"}, + {file = "rapidfuzz-2.0.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a545627a7d45ea4ad1cb66fb6ad7b951825b0e97053056cda9d2f5fbb30abe3e"}, + {file = "rapidfuzz-2.0.11-cp310-cp310-win32.whl", hash = "sha256:8e583595efe5afdd68a7b5423cbd5fff0d1870d60cee16af17897f701f39d933"}, + {file = "rapidfuzz-2.0.11-cp310-cp310-win_amd64.whl", hash = "sha256:09efd5a02a33dfb18ec6f28b85f102b51cbac080e624924f3a4f36d3b08962ef"}, + {file = "rapidfuzz-2.0.11-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c81216ecff325765bb441caf7f50a1f55aa66192aca12ec6d448b509c9387a39"}, + {file = "rapidfuzz-2.0.11-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b555dbebb413ab66c2cd394338c860094a5464f9b63faeb40ebec44271c460b"}, + {file = "rapidfuzz-2.0.11-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a007fe85dfff7a961daba13884629dcd9ab45197a2fc40749a7e8f750e7715a4"}, + {file = "rapidfuzz-2.0.11-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1031420c083681b41346267e26f9f76ef2c1544a0129fa67b07239d7a9ab9fd6"}, + {file = "rapidfuzz-2.0.11-cp36-cp36m-win32.whl", hash = "sha256:4b16147122ec4c5e4a31131b8530e674ba1b3e74e2b43b73aedc6bd0021fcae6"}, + {file = "rapidfuzz-2.0.11-cp36-cp36m-win_amd64.whl", hash = "sha256:de7559765e1da54d8d42495368e0a9852041cf8d4e077fb27811d6f009611a4b"}, + {file = "rapidfuzz-2.0.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ace59b7857e5d5b252564dd60d840667c19c00d357c7ba32e9671b68615dc49a"}, + {file = "rapidfuzz-2.0.11-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09d83e5ab57fb61a6003a3607494b1f443978e8d6b199fed3094e92f466f3bba"}, + {file = "rapidfuzz-2.0.11-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3a53797613b53e93adbf9c410260aecde5ab1d7cd1b07792be1ee4800716598"}, + {file = "rapidfuzz-2.0.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9084a550719aff3752e5a63e32d381d64b09264cabf35d5e21e6a9f0e91baca"}, + {file = "rapidfuzz-2.0.11-cp37-cp37m-win32.whl", hash = "sha256:35c8f2cae3e2079616fdf90c6b6bcf850d3810c9184c6e89a4826b6d0af88974"}, + {file = "rapidfuzz-2.0.11-cp37-cp37m-win_amd64.whl", hash = "sha256:1da580130b37a007684ab9dc6f85125e3c0d06c9f9df349e7bd52e312811c436"}, + {file = "rapidfuzz-2.0.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:757ae64598a93d0f8a21007c1abd6800f38c04e4b89167ca7b833ce30f54aef3"}, + {file = "rapidfuzz-2.0.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f5b0fd8f6bde8d89c07b76643c9f3a01e2e089b246a97b721e7fe97fdaa41820"}, + {file = "rapidfuzz-2.0.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a13cea3303b444af49417352cd11830ea2245d4e5a82bb06b6895638b81c6029"}, + {file = "rapidfuzz-2.0.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96dba6ef863cba7efd22077ba28e19a8829b523c7c7e41304c568a6ab91fc4d"}, + {file = "rapidfuzz-2.0.11-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcecc662df808ad051d9524608a3682fd80d882c93664adbaab4c7b0796e385"}, + {file = "rapidfuzz-2.0.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff74f3abd0ed473f81ba67d3207ca6db74b8b50ebae1a6734ba199a8d90d67b4"}, + {file = "rapidfuzz-2.0.11-cp38-cp38-win32.whl", hash = "sha256:b3e7eea1dd304bac4ac74a1af71da35bb68bf6060f5d6b4ad8a3e4e2c84d5110"}, + {file = "rapidfuzz-2.0.11-cp38-cp38-win_amd64.whl", hash = "sha256:5b203e83adc10dbe961a3000fa09cc47f5672d2c98c3fd2f6bb7b0df225805ee"}, + {file = "rapidfuzz-2.0.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1fe837f8b305c59549e2eec6fe8dbdd7344eeef0033fa4ee90af65f72b32c25f"}, + {file = "rapidfuzz-2.0.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc2c8aa23de4a0bef2162440f5095f606052c289059fbeb03180740783e25e6b"}, + {file = "rapidfuzz-2.0.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ebb03a6a5171233958b6adcada00c7521186ea4b78b6652b99d94d5dbf59c809"}, + {file = "rapidfuzz-2.0.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f21d8754ee49ee8da73ad5e746495a27fd29ab769f5e45ede4d8232955e0237"}, + {file = "rapidfuzz-2.0.11-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23c79ca555f188445f2e054b40a89c82e5d21b22d34f00da6e7491a6d70feff5"}, + {file = "rapidfuzz-2.0.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b10de1ea834be1f26d1f35f3e1d1f8c003c7951a7475ab9b28b5a62e9f6f0c0"}, + {file = "rapidfuzz-2.0.11-cp39-cp39-win32.whl", hash = "sha256:1bda150aca38c4d4739780c3a99c190c05101839adc10ab7804ed86000440267"}, + {file = "rapidfuzz-2.0.11-cp39-cp39-win_amd64.whl", hash = "sha256:f4ea654ef221a57b47523fe70d7423254dd285f73948b9d8c1215610d2a38e9e"}, + {file = "rapidfuzz-2.0.11-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2542f0a3c4079b15c0485ece589e5a248633de84326e4a3ca63ea024a0b59775"}, + {file = "rapidfuzz-2.0.11-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dca3c02511d23a58ef14f2fbcd7b311eae1bc40e3d36be493ef22b9572ebed1"}, + {file = "rapidfuzz-2.0.11-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbb649a978fab0232cefdeb67321d853c676b3ebb7481b8b80030905a42d799"}, + {file = "rapidfuzz-2.0.11-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:081415323e94e0016109715438d4ccb233ab038b09ba3cf79038e50601a410e9"}, + {file = "rapidfuzz-2.0.11-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a80efb64f1b38c64f04f4ca6881c9684d7912dc9124ecbf953c9b541f935b33c"}, + {file = "rapidfuzz-2.0.11-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ac8ab8106bc7b0ffab539baa5279a850c61b71ccad86dc11503bc084f6ac1af"}, + {file = "rapidfuzz-2.0.11-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3933a1cfdf6ab4e059d8eb68460fa430a4f6be06431d1a8b05f7fecdd63e586e"}, + {file = "rapidfuzz-2.0.11-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:537b72d954ff395cefc210fc7e41810a26e84ed7f1e93d0dffe3669277d6ea23"}, + {file = "rapidfuzz-2.0.11-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2d167b1bf92a60eefbaea3abf646baa2a3aa7125595e129c8890072706a80ac"}, + {file = "rapidfuzz-2.0.11.tar.gz", hash = "sha256:934b65fea75e3bd310d74903ec69ff3df061b3058ab5b7f49ab772958109bca8"}, +] requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, diff --git a/server/pyproject.toml b/server/pyproject.toml index 3a142c54..a719b9a1 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -18,6 +18,7 @@ Pillow = "^9.0.1" Flask-Caching = "^1.10.1" "colorgram.py" = "^1.2.0" tqdm = "^4.64.0" +rapidfuzz = "^2.0.11" [tool.poetry.dev-dependencies] diff --git a/server/start.sh b/server/start.sh index b86f0ca5..2bac5a6a 100755 --- a/server/start.sh +++ b/server/start.sh @@ -5,8 +5,18 @@ #python manage.py gpath=$(poetry run which gunicorn) -cd app -"$gpath" -b 0.0.0.0:9877 -w 4 --threads=2 "imgserver:app" & -echo "Booted image server" -cd ../ -"$gpath" -b 0.0.0.0:9876 -w 1 --threads=4 "manage:create_app()" #--log-level=debug +while getopts ':s' opt; do + case $opt in + s) + echo "🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴" + cd "./app" + "$gpath" -b 0.0.0.0:9877 -w 4 --threads=2 "imgserver:app" & + cd ../ + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + ;; + esac +done + +"$gpath" -b 0.0.0.0:9876 -w 1 --threads=4 "manage:create_app()" diff --git a/src/App.vue b/src/App.vue index a84e001c..8dbf3dc9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,13 +2,9 @@ -
-