From c73832bd9cd81fe9739a463ca624b977b080c822 Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Sat, 5 Aug 2023 22:45:13 -0400 Subject: [PATCH] refactor(ml): model downloading (#3545) * download facial recognition models * download hf models * simplified logic * updated `predict` for facial recognition * ensure download method is called * fixed repo_id for clip * fixed download destination * use st's own `snapshot_download` * conditional download * fixed predict method * check if loaded * minor fixes * updated mypy overrides * added pytest-mock * updated tests * updated lock --- machine-learning/app/config.py | 2 +- machine-learning/app/conftest.py | 88 +------ machine-learning/app/main.py | 6 +- machine-learning/app/models/base.py | 45 +++- machine-learning/app/models/clip.py | 15 +- .../app/models/facial_recognition.py | 75 ++++-- .../app/models/image_classification.py | 10 +- machine-learning/app/test_main.py | 131 +++++++--- machine-learning/poetry.lock | 245 ++++++++++-------- machine-learning/pyproject.toml | 7 +- 10 files changed, 350 insertions(+), 274 deletions(-) diff --git a/machine-learning/app/config.py b/machine-learning/app/config.py index f5cb835953..5714c0f4f7 100644 --- a/machine-learning/app/config.py +++ b/machine-learning/app/config.py @@ -20,7 +20,7 @@ class Settings(BaseSettings): min_face_score: float = 0.7 test_full: bool = False - class Config(BaseSettings.Config): + class Config: env_prefix = "MACHINE_LEARNING_" case_sensitive = False diff --git a/machine-learning/app/conftest.py b/machine-learning/app/conftest.py index aa73049ecc..ad1f099ea7 100644 --- a/machine-learning/app/conftest.py +++ b/machine-learning/app/conftest.py @@ -1,5 +1,4 @@ -from types import SimpleNamespace -from typing import Any, Iterator, TypeAlias +from typing import Iterator, TypeAlias from unittest import mock import numpy as np @@ -22,91 +21,6 @@ def cv_image(pil_image: Image.Image) -> ndarray: return np.asarray(pil_image)[:, :, ::-1] # PIL uses RGB while cv2 uses BGR -@pytest.fixture -def mock_classifier_pipeline() -> Iterator[mock.Mock]: - with mock.patch("app.models.image_classification.pipeline") as model: - classifier_preds = [ - {"label": "that's an image alright", "score": 0.8}, - {"label": "well it ends with .jpg", "score": 0.1}, - {"label": "idk, im just seeing bytes", "score": 0.05}, - {"label": "not sure", "score": 0.04}, - {"label": "probably a virus", "score": 0.01}, - ] - - def forward( - inputs: Image.Image | list[Image.Image], **kwargs: Any - ) -> list[dict[str, Any]] | list[list[dict[str, Any]]]: - if isinstance(inputs, list) and not all([isinstance(img, Image.Image) for img in inputs]): - raise TypeError - elif not isinstance(inputs, Image.Image): - raise TypeError - - if isinstance(inputs, list): - return [classifier_preds] * len(inputs) - - return classifier_preds - - model.return_value = forward - yield model - - -@pytest.fixture -def mock_st() -> Iterator[mock.Mock]: - with mock.patch("app.models.clip.SentenceTransformer") as model: - embedding = np.random.rand(512).astype(np.float32) - - def encode(inputs: Image.Image | list[Image.Image], **kwargs: Any) -> ndarray | list[ndarray]: - # mypy complains unless isinstance(inputs, list) is used explicitly - img_batch = isinstance(inputs, list) and all([isinstance(inst, Image.Image) for inst in inputs]) - text_batch = isinstance(inputs, list) and all([isinstance(inst, str) for inst in inputs]) - if isinstance(inputs, list) and not any([img_batch, text_batch]): - raise TypeError - - if isinstance(inputs, list): - return np.stack([embedding] * len(inputs)) - - return embedding - - mocked = mock.Mock() - mocked.encode = encode - model.return_value = mocked - yield model - - -@pytest.fixture -def mock_faceanalysis() -> Iterator[mock.Mock]: - with mock.patch("app.models.facial_recognition.FaceAnalysis") as model: - face_preds = [ - SimpleNamespace( # this is so these fields can be accessed through dot notation - **{ - "bbox": np.random.rand(4).astype(np.float32), - "kps": np.random.rand(5, 2).astype(np.float32), - "det_score": np.array([0.67]).astype(np.float32), - "normed_embedding": np.random.rand(512).astype(np.float32), - } - ), - SimpleNamespace( - **{ - "bbox": np.random.rand(4).astype(np.float32), - "kps": np.random.rand(5, 2).astype(np.float32), - "det_score": np.array([0.4]).astype(np.float32), - "normed_embedding": np.random.rand(512).astype(np.float32), - } - ), - ] - - def get(image: np.ndarray[int, np.dtype[np.float32]], **kwargs: Any) -> list[SimpleNamespace]: - if not isinstance(image, np.ndarray): - raise TypeError - - return face_preds - - mocked = mock.Mock() - mocked.get = get - model.return_value = mocked - yield model - - @pytest.fixture def mock_get_model() -> Iterator[mock.Mock]: with mock.patch("app.models.cache.InferenceModel.from_model_type", autospec=True) as mocked: diff --git a/machine-learning/app/main.py b/machine-learning/app/main.py index 264eb2ee87..59327d575c 100644 --- a/machine-learning/app/main.py +++ b/machine-learning/app/main.py @@ -9,7 +9,6 @@ from fastapi import Body, Depends, FastAPI from PIL import Image from .config import settings -from .models.base import InferenceModel from .models.cache import ModelCache from .schemas import ( EmbeddingResponse, @@ -38,10 +37,7 @@ async def load_models() -> None: # Get all models for model_name, model_type in models: - if settings.eager_startup: - await app.state.model_cache.get(model_name, model_type) - else: - InferenceModel.from_model_type(model_type, model_name) + await app.state.model_cache.get(model_name, model_type, eager=settings.eager_startup) @app.on_event("startup") diff --git a/machine-learning/app/models/base.py b/machine-learning/app/models/base.py index 98d6fb8349..8c3a06fc92 100644 --- a/machine-learning/app/models/base.py +++ b/machine-learning/app/models/base.py @@ -14,22 +14,43 @@ from ..schemas import ModelType class InferenceModel(ABC): _model_type: ModelType - def __init__(self, model_name: str, cache_dir: Path | str | None = None, **model_kwargs: Any) -> None: + def __init__( + self, model_name: str, cache_dir: Path | str | None = None, eager: bool = True, **model_kwargs: Any + ) -> None: self.model_name = model_name + self._loaded = False self._cache_dir = Path(cache_dir) if cache_dir is not None else get_cache_dir(model_name, self.model_type) - + loader = self.load if eager else self.download try: - self.load(**model_kwargs) + loader(**model_kwargs) except (OSError, InvalidProtobuf): self.clear_cache() - self.load(**model_kwargs) + loader(**model_kwargs) + + def download(self, **model_kwargs: Any) -> None: + if not self.cached: + self._download(**model_kwargs) + + def load(self, **model_kwargs: Any) -> None: + self.download(**model_kwargs) + self._load(**model_kwargs) + self._loaded = True + + def predict(self, inputs: Any) -> Any: + if not self._loaded: + self.load() + return self._predict(inputs) @abstractmethod - def load(self, **model_kwargs: Any) -> None: + def _predict(self, inputs: Any) -> Any: ... @abstractmethod - def predict(self, inputs: Any) -> Any: + def _download(self, **model_kwargs: Any) -> None: + ... + + @abstractmethod + def _load(self, **model_kwargs: Any) -> None: ... @property @@ -44,6 +65,10 @@ class InferenceModel(ABC): def cache_dir(self, cache_dir: Path) -> None: self._cache_dir = cache_dir + @property + def cached(self) -> bool: + return self.cache_dir.exists() and any(self.cache_dir.iterdir()) + @classmethod def from_model_type(cls, model_type: ModelType, model_name: str, **model_kwargs: Any) -> InferenceModel: subclasses = {subclass._model_type: subclass for subclass in cls.__subclasses__()} @@ -55,7 +80,11 @@ class InferenceModel(ABC): def clear_cache(self) -> None: if not self.cache_dir.exists(): return - elif not rmtree.avoids_symlink_attacks: + if not rmtree.avoids_symlink_attacks: raise RuntimeError("Attempted to clear cache, but rmtree is not safe on this platform.") - rmtree(self.cache_dir) + if self.cache_dir.is_dir(): + rmtree(self.cache_dir) + else: + self.cache_dir.unlink() + self.cache_dir.mkdir(parents=True, exist_ok=True) diff --git a/machine-learning/app/models/clip.py b/machine-learning/app/models/clip.py index ac9d800cf4..875671d391 100644 --- a/machine-learning/app/models/clip.py +++ b/machine-learning/app/models/clip.py @@ -1,8 +1,8 @@ -from pathlib import Path from typing import Any from PIL.Image import Image from sentence_transformers import SentenceTransformer +from sentence_transformers.util import snapshot_download from ..schemas import ModelType from .base import InferenceModel @@ -11,12 +11,21 @@ from .base import InferenceModel class CLIPSTEncoder(InferenceModel): _model_type = ModelType.CLIP - def load(self, **model_kwargs: Any) -> None: + def _download(self, **model_kwargs: Any) -> None: + repo_id = self.model_name if "/" in self.model_name else f"sentence-transformers/{self.model_name}" + snapshot_download( + cache_dir=self.cache_dir, + repo_id=repo_id, + library_name="sentence-transformers", + ignore_files=["flax_model.msgpack", "rust_model.ot", "tf_model.h5"], + ) + + def _load(self, **model_kwargs: Any) -> None: self.model = SentenceTransformer( self.model_name, cache_folder=self.cache_dir.as_posix(), **model_kwargs, ) - def predict(self, image_or_text: Image | str) -> list[float]: + def _predict(self, image_or_text: Image | str) -> list[float]: return self.model.encode(image_or_text).tolist() diff --git a/machine-learning/app/models/facial_recognition.py b/machine-learning/app/models/facial_recognition.py index b9f96b7b44..32ea629dfc 100644 --- a/machine-learning/app/models/facial_recognition.py +++ b/machine-learning/app/models/facial_recognition.py @@ -1,8 +1,12 @@ +import zipfile from pathlib import Path from typing import Any import cv2 -from insightface.app import FaceAnalysis +import numpy as np +from insightface.model_zoo import ArcFaceONNX, RetinaFace +from insightface.utils.face_align import norm_crop +from insightface.utils.storage import BASE_REPO_URL, download_file from ..config import settings from ..schemas import ModelType @@ -22,39 +26,62 @@ class FaceRecognizer(InferenceModel): self.min_score = min_score super().__init__(model_name, cache_dir, **model_kwargs) - def load(self, **model_kwargs: Any) -> None: - self.model = FaceAnalysis( - name=self.model_name, - root=self.cache_dir.as_posix(), - allowed_modules=["detection", "recognition"], - **model_kwargs, - ) - self.model.prepare( - ctx_id=0, + def _download(self, **model_kwargs: Any) -> None: + zip_file = self.cache_dir / f"{self.model_name}.zip" + download_file(f"{BASE_REPO_URL}/{self.model_name}.zip", zip_file) + with zipfile.ZipFile(zip_file, "r") as zip: + members = zip.namelist() + det_file = next(model for model in members if model.startswith("det_")) + rec_file = next(model for model in members if model.startswith("w600k_")) + zip.extractall(self.cache_dir, members=[det_file, rec_file]) + zip_file.unlink() + + def _load(self, **model_kwargs: Any) -> None: + try: + det_file = next(self.cache_dir.glob("det_*.onnx")) + rec_file = next(self.cache_dir.glob("w600k_*.onnx")) + except StopIteration: + raise FileNotFoundError("Facial recognition models not found in cache directory") + self.det_model = RetinaFace(det_file.as_posix()) + self.rec_model = ArcFaceONNX(rec_file.as_posix()) + + self.det_model.prepare( + ctx_id=-1, det_thresh=self.min_score, - det_size=(640, 640), + input_size=(640, 640), ) + self.rec_model.prepare(ctx_id=-1) + + def _predict(self, image: cv2.Mat) -> list[dict[str, Any]]: + bboxes, kpss = self.det_model.detect(image) + if bboxes.size == 0: + return [] + assert isinstance(kpss, np.ndarray) + + scores = bboxes[:, 4].tolist() + bboxes = bboxes[:, :4].round().tolist() - def predict(self, image: cv2.Mat) -> list[dict[str, Any]]: - height, width, _ = image.shape results = [] - faces = self.model.get(image) - - for face in faces: - x1, y1, x2, y2 = face.bbox - + height, width, _ = image.shape + for (x1, y1, x2, y2), score, kps in zip(bboxes, scores, kpss): + cropped_img = norm_crop(image, kps) + embedding = self.rec_model.get_feat(cropped_img)[0].tolist() results.append( { "imageWidth": width, "imageHeight": height, "boundingBox": { - "x1": round(x1), - "y1": round(y1), - "x2": round(x2), - "y2": round(y2), + "x1": x1, + "y1": y1, + "x2": x2, + "y2": y2, }, - "score": face.det_score.item(), - "embedding": face.normed_embedding.tolist(), + "score": score, + "embedding": embedding, } ) return results + + @property + def cached(self) -> bool: + return self.cache_dir.is_dir() and any(self.cache_dir.glob("*.onnx")) diff --git a/machine-learning/app/models/image_classification.py b/machine-learning/app/models/image_classification.py index 0b5887f53f..9a9ba42194 100644 --- a/machine-learning/app/models/image_classification.py +++ b/machine-learning/app/models/image_classification.py @@ -1,6 +1,7 @@ from pathlib import Path from typing import Any +from huggingface_hub import snapshot_download from PIL.Image import Image from transformers.pipelines import pipeline @@ -22,14 +23,19 @@ class ImageClassifier(InferenceModel): self.min_score = min_score super().__init__(model_name, cache_dir, **model_kwargs) - def load(self, **model_kwargs: Any) -> None: + def _download(self, **model_kwargs: Any) -> None: + snapshot_download( + cache_dir=self.cache_dir, repo_id=self.model_name, allow_patterns=["*.bin", "*.json", "*.txt"] + ) + + def _load(self, **model_kwargs: Any) -> None: self.model = pipeline( self.model_type.value, self.model_name, model_kwargs={"cache_dir": self.cache_dir, **model_kwargs}, ) - def predict(self, image: Image) -> list[str]: + def _predict(self, image: Image) -> list[str]: predictions: list[dict[str, Any]] = self.model(image) # type: ignore tags = [tag for pred in predictions for tag in pred["label"].split(", ") if pred["score"] >= self.min_score] diff --git a/machine-learning/app/test_main.py b/machine-learning/app/test_main.py index 11a0466c83..465624004f 100644 --- a/machine-learning/app/test_main.py +++ b/machine-learning/app/test_main.py @@ -1,11 +1,13 @@ from io import BytesIO -from pathlib import Path +from typing import TypeAlias from unittest import mock import cv2 +import numpy as np import pytest from fastapi.testclient import TestClient from PIL import Image +from pytest_mock import MockerFixture from .config import settings from .models.cache import ModelCache @@ -14,22 +16,43 @@ from .models.facial_recognition import FaceRecognizer from .models.image_classification import ImageClassifier from .schemas import ModelType +ndarray: TypeAlias = np.ndarray[int, np.dtype[np.float32]] + class TestImageClassifier: - def test_init(self, mock_classifier_pipeline: mock.Mock) -> None: - cache_dir = Path("test_cache") - classifier = ImageClassifier("test_model_name", 0.5, cache_dir=cache_dir) + classifier_preds = [ + {"label": "that's an image alright", "score": 0.8}, + {"label": "well it ends with .jpg", "score": 0.1}, + {"label": "idk, im just seeing bytes", "score": 0.05}, + {"label": "not sure", "score": 0.04}, + {"label": "probably a virus", "score": 0.01}, + ] - assert classifier.min_score == 0.5 - mock_classifier_pipeline.assert_called_once_with( - "image-classification", - "test_model_name", - model_kwargs={"cache_dir": cache_dir}, - ) + def test_eager_init(self, mocker: MockerFixture) -> None: + mocker.patch.object(ImageClassifier, "download") + mock_load = mocker.patch.object(ImageClassifier, "load") + classifier = ImageClassifier("test_model_name", cache_dir="test_cache", eager=True, test_arg="test_arg") - def test_min_score(self, pil_image: Image.Image, mock_classifier_pipeline: mock.Mock) -> None: + assert classifier.model_name == "test_model_name" + mock_load.assert_called_once_with(test_arg="test_arg") + + def test_lazy_init(self, mocker: MockerFixture) -> None: + mock_download = mocker.patch.object(ImageClassifier, "download") + mock_load = mocker.patch.object(ImageClassifier, "load") + face_model = ImageClassifier("test_model_name", cache_dir="test_cache", eager=False, test_arg="test_arg") + + assert face_model.model_name == "test_model_name" + mock_download.assert_called_once_with(test_arg="test_arg") + mock_load.assert_not_called() + + def test_min_score(self, pil_image: Image.Image, mocker: MockerFixture) -> None: + mocker.patch.object(ImageClassifier, "load") classifier = ImageClassifier("test_model_name", min_score=0.0) - classifier.min_score = 0.0 + assert classifier.min_score == 0.0 + + classifier.model = mock.Mock() + classifier.model.return_value = self.classifier_preds + all_labels = classifier.predict(pil_image) classifier.min_score = 0.5 filtered_labels = classifier.predict(pil_image) @@ -46,45 +69,94 @@ class TestImageClassifier: class TestCLIP: - def test_init(self, mock_st: mock.Mock) -> None: - CLIPSTEncoder("test_model_name", cache_dir="test_cache") + embedding = np.random.rand(512).astype(np.float32) - mock_st.assert_called_once_with("test_model_name", cache_folder="test_cache") + def test_eager_init(self, mocker: MockerFixture) -> None: + mocker.patch.object(CLIPSTEncoder, "download") + mock_load = mocker.patch.object(CLIPSTEncoder, "load") + clip_model = CLIPSTEncoder("test_model_name", cache_dir="test_cache", eager=True, test_arg="test_arg") - def test_basic_image(self, pil_image: Image.Image, mock_st: mock.Mock) -> None: + assert clip_model.model_name == "test_model_name" + mock_load.assert_called_once_with(test_arg="test_arg") + + def test_lazy_init(self, mocker: MockerFixture) -> None: + mock_download = mocker.patch.object(CLIPSTEncoder, "download") + mock_load = mocker.patch.object(CLIPSTEncoder, "load") + clip_model = CLIPSTEncoder("test_model_name", cache_dir="test_cache", eager=False, test_arg="test_arg") + + assert clip_model.model_name == "test_model_name" + mock_download.assert_called_once_with(test_arg="test_arg") + mock_load.assert_not_called() + + def test_basic_image(self, pil_image: Image.Image, mocker: MockerFixture) -> None: + mocker.patch.object(CLIPSTEncoder, "load") clip_encoder = CLIPSTEncoder("test_model_name", cache_dir="test_cache") + clip_encoder.model = mock.Mock() + clip_encoder.model.encode.return_value = self.embedding embedding = clip_encoder.predict(pil_image) assert isinstance(embedding, list) assert len(embedding) == 512 assert all([isinstance(num, float) for num in embedding]) - mock_st.assert_called_once() + clip_encoder.model.encode.assert_called_once() - def test_basic_text(self, mock_st: mock.Mock) -> None: + def test_basic_text(self, mocker: MockerFixture) -> None: + mocker.patch.object(CLIPSTEncoder, "load") clip_encoder = CLIPSTEncoder("test_model_name", cache_dir="test_cache") + clip_encoder.model = mock.Mock() + clip_encoder.model.encode.return_value = self.embedding embedding = clip_encoder.predict("test search query") assert isinstance(embedding, list) assert len(embedding) == 512 assert all([isinstance(num, float) for num in embedding]) - mock_st.assert_called_once() + clip_encoder.model.encode.assert_called_once() class TestFaceRecognition: - def test_init(self, mock_faceanalysis: mock.Mock) -> None: - FaceRecognizer("test_model_name", cache_dir="test_cache") + def test_eager_init(self, mocker: MockerFixture) -> None: + mocker.patch.object(FaceRecognizer, "download") + mock_load = mocker.patch.object(FaceRecognizer, "load") + face_model = FaceRecognizer("test_model_name", cache_dir="test_cache", eager=True, test_arg="test_arg") - mock_faceanalysis.assert_called_once_with( - name="test_model_name", - root="test_cache", - allowed_modules=["detection", "recognition"], - ) + assert face_model.model_name == "test_model_name" + mock_load.assert_called_once_with(test_arg="test_arg") - def test_basic(self, cv_image: cv2.Mat, mock_faceanalysis: mock.Mock) -> None: + def test_lazy_init(self, mocker: MockerFixture) -> None: + mock_download = mocker.patch.object(FaceRecognizer, "download") + mock_load = mocker.patch.object(FaceRecognizer, "load") + face_model = FaceRecognizer("test_model_name", cache_dir="test_cache", eager=False, test_arg="test_arg") + + assert face_model.model_name == "test_model_name" + mock_download.assert_called_once_with(test_arg="test_arg") + mock_load.assert_not_called() + + def test_set_min_score(self, mocker: MockerFixture) -> None: + mocker.patch.object(FaceRecognizer, "load") + face_recognizer = FaceRecognizer("test_model_name", cache_dir="test_cache", min_score=0.5) + + assert face_recognizer.min_score == 0.5 + + def test_basic(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None: + mocker.patch.object(FaceRecognizer, "load") face_recognizer = FaceRecognizer("test_model_name", min_score=0.0, cache_dir="test_cache") + + det_model = mock.Mock() + num_faces = 2 + bbox = np.random.rand(num_faces, 4).astype(np.float32) + score = np.array([[0.67]] * num_faces).astype(np.float32) + kpss = np.random.rand(num_faces, 5, 2).astype(np.float32) + det_model.detect.return_value = (np.concatenate([bbox, score], axis=-1), kpss) + face_recognizer.det_model = det_model + + rec_model = mock.Mock() + embedding = np.random.rand(num_faces, 512).astype(np.float32) + rec_model.get_feat.return_value = embedding + face_recognizer.rec_model = rec_model + faces = face_recognizer.predict(cv_image) - assert len(faces) == 2 + assert len(faces) == num_faces for face in faces: assert face["imageHeight"] == 800 assert face["imageWidth"] == 600 @@ -92,7 +164,8 @@ class TestFaceRecognition: assert len(face["embedding"]) == 512 assert all([isinstance(num, float) for num in face["embedding"]]) - mock_faceanalysis.assert_called_once() + det_model.detect.assert_called_once() + assert rec_model.get_feat.call_count == num_faces @pytest.mark.asyncio diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock index f40f86cc3e..bb59ee5c41 100644 --- a/machine-learning/poetry.lock +++ b/machine-learning/poetry.lock @@ -421,13 +421,13 @@ cron = ["capturer (>=2.4)"] [[package]] name = "configargparse" -version = "1.5.5" +version = "1.7" description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5" files = [ - {file = "ConfigArgParse-1.5.5-py3-none-any.whl", hash = "sha256:541360ddc1b15c517f95c0d02d1fca4591266628f3667acdc5d13dccc78884ca"}, - {file = "ConfigArgParse-1.5.5.tar.gz", hash = "sha256:363d80a6d35614bd446e2f2b1b216f3b33741d03ac6d0a92803306f40e555b58"}, + {file = "ConfigArgParse-1.7-py3-none-any.whl", hash = "sha256:d249da6591465c6c26df64a9f73d2536e743be2f244eb3ebe61114af2f94f86b"}, + {file = "ConfigArgParse-1.7.tar.gz", hash = "sha256:e7067471884de5478c58a511e529f0f9bd1c66bfef1dea90935438d6c23306d1"}, ] [package.extras] @@ -750,45 +750,45 @@ files = [ [[package]] name = "fonttools" -version = "4.41.1" +version = "4.42.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.41.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a7bbb290d13c6dd718ec2c3db46fe6c5f6811e7ea1e07f145fd8468176398224"}, - {file = "fonttools-4.41.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec453a45778524f925a8f20fd26a3326f398bfc55d534e37bab470c5e415caa1"}, - {file = "fonttools-4.41.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2071267deaa6d93cb16288613419679c77220543551cbe61da02c93d92df72f"}, - {file = "fonttools-4.41.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e3334d51f0e37e2c6056e67141b2adabc92613a968797e2571ca8a03bd64773"}, - {file = "fonttools-4.41.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cac73bbef7734e78c60949da11c4903ee5837168e58772371bd42a75872f4f82"}, - {file = "fonttools-4.41.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:edee0900cf0eedb29d17c7876102d6e5a91ee333882b1f5abc83e85b934cadb5"}, - {file = "fonttools-4.41.1-cp310-cp310-win32.whl", hash = "sha256:2a22b2c425c698dcd5d6b0ff0b566e8e9663172118db6fd5f1941f9b8063da9b"}, - {file = "fonttools-4.41.1-cp310-cp310-win_amd64.whl", hash = "sha256:547ab36a799dded58a46fa647266c24d0ed43a66028cd1cd4370b246ad426cac"}, - {file = "fonttools-4.41.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:849ec722bbf7d3501a0e879e57dec1fc54919d31bff3f690af30bb87970f9784"}, - {file = "fonttools-4.41.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38cdecd8f1fd4bf4daae7fed1b3170dfc1b523388d6664b2204b351820aa78a7"}, - {file = "fonttools-4.41.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ae64303ba670f8959fdaaa30ba0c2dabe75364fdec1caeee596c45d51ca3425"}, - {file = "fonttools-4.41.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14f3ccea4cc7dd1b277385adf3c3bf18f9860f87eab9c2fb650b0af16800f55"}, - {file = "fonttools-4.41.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:33191f062549e6bb1a4782c22a04ebd37009c09360e2d6686ac5083774d06d95"}, - {file = "fonttools-4.41.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:704bccd69b0abb6fab9f5e4d2b75896afa48b427caa2c7988792a2ffce35b441"}, - {file = "fonttools-4.41.1-cp311-cp311-win32.whl", hash = "sha256:4edc795533421e98f60acee7d28fc8d941ff5ac10f44668c9c3635ad72ae9045"}, - {file = "fonttools-4.41.1-cp311-cp311-win_amd64.whl", hash = "sha256:aaaef294d8e411f0ecb778a0aefd11bb5884c9b8333cc1011bdaf3b58ca4bd75"}, - {file = "fonttools-4.41.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3d1f9471134affc1e3b1b806db6e3e2ad3fa99439e332f1881a474c825101096"}, - {file = "fonttools-4.41.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:59eba8b2e749a1de85760da22333f3d17c42b66e03758855a12a2a542723c6e7"}, - {file = "fonttools-4.41.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9b3cc10dc9e0834b6665fd63ae0c6964c6bc3d7166e9bc84772e0edd09f9fa2"}, - {file = "fonttools-4.41.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2c2964bdc827ba6b8a91dc6de792620be4da3922c4cf0599f36a488c07e2b2"}, - {file = "fonttools-4.41.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7763316111df7b5165529f4183a334aa24c13cdb5375ffa1dc8ce309c8bf4e5c"}, - {file = "fonttools-4.41.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b2d1ee95be42b80d1f002d1ee0a51d7a435ea90d36f1a5ae331be9962ee5a3f1"}, - {file = "fonttools-4.41.1-cp38-cp38-win32.whl", hash = "sha256:f48602c0b3fd79cd83a34c40af565fe6db7ac9085c8823b552e6e751e3a5b8be"}, - {file = "fonttools-4.41.1-cp38-cp38-win_amd64.whl", hash = "sha256:b0938ebbeccf7c80bb9a15e31645cf831572c3a33d5cc69abe436e7000c61b14"}, - {file = "fonttools-4.41.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e5c2b0a95a221838991e2f0e455dec1ca3a8cc9cd54febd68cc64d40fdb83669"}, - {file = "fonttools-4.41.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:891cfc5a83b0307688f78b9bb446f03a7a1ad981690ac8362f50518bc6153975"}, - {file = "fonttools-4.41.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73ef0bb5d60eb02ba4d3a7d23ada32184bd86007cb2de3657cfcb1175325fc83"}, - {file = "fonttools-4.41.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f240d9adf0583ac8fc1646afe7f4ac039022b6f8fa4f1575a2cfa53675360b69"}, - {file = "fonttools-4.41.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bdd729744ae7ecd7f7311ad25d99da4999003dcfe43b436cf3c333d4e68de73d"}, - {file = "fonttools-4.41.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b927e5f466d99c03e6e20961946314b81d6e3490d95865ef88061144d9f62e38"}, - {file = "fonttools-4.41.1-cp39-cp39-win32.whl", hash = "sha256:afce2aeb80be72b4da7dd114f10f04873ff512793d13ce0b19d12b2a4c44c0f0"}, - {file = "fonttools-4.41.1-cp39-cp39-win_amd64.whl", hash = "sha256:1df1b6f4c7c4bc8201eb47f3b268adbf2539943aa43c400f84556557e3e109c0"}, - {file = "fonttools-4.41.1-py3-none-any.whl", hash = "sha256:952cb405f78734cf6466252fec42e206450d1a6715746013f64df9cbd4f896fa"}, - {file = "fonttools-4.41.1.tar.gz", hash = "sha256:e16a9449f21a93909c5be2f5ed5246420f2316e94195dbfccb5238aaa38f9751"}, + {file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9c456d1f23deff64ffc8b5b098718e149279abdea4d8692dba69172fb6a0d597"}, + {file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:150122ed93127a26bc3670ebab7e2add1e0983d30927733aec327ebf4255b072"}, + {file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48e82d776d2e93f88ca56567509d102266e7ab2fb707a0326f032fe657335238"}, + {file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58c1165f9b2662645de9b19a8c8bdd636b36294ccc07e1b0163856b74f10bafc"}, + {file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d6dc3fa91414ff4daa195c05f946e6a575bd214821e26d17ca50f74b35b0fe4"}, + {file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fae4e801b774cc62cecf4a57b1eae4097903fced00c608d9e2bc8f84cd87b54a"}, + {file = "fonttools-4.42.0-cp310-cp310-win32.whl", hash = "sha256:b8600ae7dce6ec3ddfb201abb98c9d53abbf8064d7ac0c8a0d8925e722ccf2a0"}, + {file = "fonttools-4.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:57b68eab183fafac7cd7d464a7bfa0fcd4edf6c67837d14fb09c1c20516cf20b"}, + {file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0a1466713e54bdbf5521f2f73eebfe727a528905ff5ec63cda40961b4b1eea95"}, + {file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3fb2a69870bfe143ec20b039a1c8009e149dd7780dd89554cc8a11f79e5de86b"}, + {file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae881e484702efdb6cf756462622de81d4414c454edfd950b137e9a7352b3cb9"}, + {file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27ec3246a088555629f9f0902f7412220c67340553ca91eb540cf247aacb1983"}, + {file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ece1886d12bb36c48c00b2031518877f41abae317e3a55620d38e307d799b7e"}, + {file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10dac980f2b975ef74532e2a94bb00e97a95b4595fb7f98db493c474d5f54d0e"}, + {file = "fonttools-4.42.0-cp311-cp311-win32.whl", hash = "sha256:83b98be5d291e08501bd4fc0c4e0f8e6e05b99f3924068b17c5c9972af6fff84"}, + {file = "fonttools-4.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:e35bed436726194c5e6e094fdfb423fb7afaa0211199f9d245e59e11118c576c"}, + {file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c36c904ce0322df01e590ba814d5d69e084e985d7e4c2869378671d79662a7d4"}, + {file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d54e600a2bcfa5cdaa860237765c01804a03b08404d6affcd92942fa7315ffba"}, + {file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01cfe02416b6d416c5c8d15e30315cbcd3e97d1b50d3b34b0ce59f742ef55258"}, + {file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f81ed9065b4bd3f4f3ce8e4873cd6a6b3f4e92b1eddefde35d332c6f414acc3"}, + {file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:685a4dd6cf31593b50d6d441feb7781a4a7ef61e19551463e14ed7c527b86f9f"}, + {file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:329341ba3d86a36e482610db56b30705384cb23bd595eac8cbb045f627778e9d"}, + {file = "fonttools-4.42.0-cp38-cp38-win32.whl", hash = "sha256:4655c480a1a4d706152ff54f20e20cf7609084016f1df3851cce67cef768f40a"}, + {file = "fonttools-4.42.0-cp38-cp38-win_amd64.whl", hash = "sha256:6bd7e4777bff1dcb7c4eff4786998422770f3bfbef8be401c5332895517ba3fa"}, + {file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9b55d2a3b360e0c7fc5bd8badf1503ca1c11dd3a1cd20f2c26787ffa145a9c7"}, + {file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0df8ef75ba5791e873c9eac2262196497525e3f07699a2576d3ab9ddf41cb619"}, + {file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd2363ea7728496827658682d049ffb2e98525e2247ca64554864a8cc945568"}, + {file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d40673b2e927f7cd0819c6f04489dfbeb337b4a7b10fc633c89bf4f34ecb9620"}, + {file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c8bf88f9e3ce347c716921804ef3a8330cb128284eb6c0b6c4b3574f3c580023"}, + {file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:703101eb0490fae32baf385385d47787b73d9ea55253df43b487c89ec767e0d7"}, + {file = "fonttools-4.42.0-cp39-cp39-win32.whl", hash = "sha256:f0290ea7f9945174bd4dfd66e96149037441eb2008f3649094f056201d99e293"}, + {file = "fonttools-4.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:ae7df0ae9ee2f3f7676b0ff6f4ebe48ad0acaeeeaa0b6839d15dbf0709f2c5ef"}, + {file = "fonttools-4.42.0-py3-none-any.whl", hash = "sha256:dfe7fa7e607f7e8b58d0c32501a3a7cac148538300626d1b930082c90ae7f6bd"}, + {file = "fonttools-4.42.0.tar.gz", hash = "sha256:614b1283dca88effd20ee48160518e6de275ce9b5456a3134d5f235523fc5065"}, ] [package.extras] @@ -1525,13 +1525,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] [[package]] name = "locust" -version = "2.15.1" +version = "2.16.1" description = "Developer friendly load testing framework" optional = false python-versions = ">=3.7" files = [ - {file = "locust-2.15.1-py3-none-any.whl", hash = "sha256:9e0bb30b4962f9c9611174df0fdea2a4e3f41656b36dc7b0a1a46f618a83d5a9"}, - {file = "locust-2.15.1.tar.gz", hash = "sha256:a6307f3bf995c180f66e7caed94360b8c8ed95d64dca508614d803d5b0b39f15"}, + {file = "locust-2.16.1-py3-none-any.whl", hash = "sha256:d0f01f9fca6a7d9be987b32185799d9e219fce3b9a3b8250ea03e88003335804"}, + {file = "locust-2.16.1.tar.gz", hash = "sha256:cd54f179b679ae927e9b3ffd2b6a7c89c1078103cfbe96b4dd53c7872774b619"}, ] [package.dependencies] @@ -1860,36 +1860,36 @@ twitter = ["twython"] [[package]] name = "numpy" -version = "1.25.1" +version = "1.25.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.25.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77d339465dff3eb33c701430bcb9c325b60354698340229e1dff97745e6b3efa"}, - {file = "numpy-1.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d736b75c3f2cb96843a5c7f8d8ccc414768d34b0a75f466c05f3a739b406f10b"}, - {file = "numpy-1.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a90725800caeaa160732d6b31f3f843ebd45d6b5f3eec9e8cc287e30f2805bf"}, - {file = "numpy-1.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c6c9261d21e617c6dc5eacba35cb68ec36bb72adcff0dee63f8fbc899362588"}, - {file = "numpy-1.25.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0def91f8af6ec4bb94c370e38c575855bf1d0be8a8fbfba42ef9c073faf2cf19"}, - {file = "numpy-1.25.1-cp310-cp310-win32.whl", hash = "sha256:fd67b306320dcadea700a8f79b9e671e607f8696e98ec255915c0c6d6b818503"}, - {file = "numpy-1.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:c1516db588987450b85595586605742879e50dcce923e8973f79529651545b57"}, - {file = "numpy-1.25.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b82655dd8efeea69dbf85d00fca40013d7f503212bc5259056244961268b66e"}, - {file = "numpy-1.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e8f6049c4878cb16960fbbfb22105e49d13d752d4d8371b55110941fb3b17800"}, - {file = "numpy-1.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41a56b70e8139884eccb2f733c2f7378af06c82304959e174f8e7370af112e09"}, - {file = "numpy-1.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5154b1a25ec796b1aee12ac1b22f414f94752c5f94832f14d8d6c9ac40bcca6"}, - {file = "numpy-1.25.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38eb6548bb91c421261b4805dc44def9ca1a6eef6444ce35ad1669c0f1a3fc5d"}, - {file = "numpy-1.25.1-cp311-cp311-win32.whl", hash = "sha256:791f409064d0a69dd20579345d852c59822c6aa087f23b07b1b4e28ff5880fcb"}, - {file = "numpy-1.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:c40571fe966393b212689aa17e32ed905924120737194b5d5c1b20b9ed0fb171"}, - {file = "numpy-1.25.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3d7abcdd85aea3e6cdddb59af2350c7ab1ed764397f8eec97a038ad244d2d105"}, - {file = "numpy-1.25.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a180429394f81c7933634ae49b37b472d343cccb5bb0c4a575ac8bbc433722f"}, - {file = "numpy-1.25.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d412c1697c3853c6fc3cb9751b4915859c7afe6a277c2bf00acf287d56c4e625"}, - {file = "numpy-1.25.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20e1266411120a4f16fad8efa8e0454d21d00b8c7cee5b5ccad7565d95eb42dd"}, - {file = "numpy-1.25.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f76aebc3358ade9eacf9bc2bb8ae589863a4f911611694103af05346637df1b7"}, - {file = "numpy-1.25.1-cp39-cp39-win32.whl", hash = "sha256:247d3ffdd7775bdf191f848be8d49100495114c82c2bd134e8d5d075fb386a1c"}, - {file = "numpy-1.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:1d5d3c68e443c90b38fdf8ef40e60e2538a27548b39b12b73132456847f4b631"}, - {file = "numpy-1.25.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:35a9527c977b924042170a0887de727cd84ff179e478481404c5dc66b4170009"}, - {file = "numpy-1.25.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d3fe3dd0506a28493d82dc3cf254be8cd0d26f4008a417385cbf1ae95b54004"}, - {file = "numpy-1.25.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:012097b5b0d00a11070e8f2e261128c44157a8689f7dedcf35576e525893f4fe"}, - {file = "numpy-1.25.1.tar.gz", hash = "sha256:9a3a9f3a61480cc086117b426a8bd86869c213fc4072e606f01c4e4b66eb92bf"}, + {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, + {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, + {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, + {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, + {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, + {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, + {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, + {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, + {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, + {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, + {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, + {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, ] [[package]] @@ -2020,13 +2020,13 @@ files = [ [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] @@ -2110,18 +2110,18 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "3.9.1" +version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, - {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" @@ -2215,47 +2215,47 @@ files = [ [[package]] name = "pydantic" -version = "1.10.11" +version = "1.10.12" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"}, - {file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"}, - {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"}, - {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"}, - {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"}, - {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"}, - {file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"}, - {file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"}, - {file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"}, - {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"}, - {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"}, - {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"}, - {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"}, - {file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"}, - {file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"}, - {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"}, - {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"}, - {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"}, - {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"}, - {file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"}, - {file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"}, - {file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"}, - {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"}, - {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"}, - {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"}, - {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"}, - {file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"}, - {file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"}, - {file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"}, - {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"}, - {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"}, - {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"}, - {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"}, - {file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"}, - {file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"}, - {file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, + {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, + {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, + {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, + {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, + {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, + {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, + {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, + {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, ] [package.dependencies] @@ -2346,6 +2346,23 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "pytest-mock" +version = "3.11.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, + {file = "pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -3664,4 +3681,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "4a06d26614d016bfdbb290ad93b3c71378ad03b249a8f06cb53c82465862977f" +content-hash = "0a4f26164e0dd32ce9d63da9322739c0812e56a5bdfb4148c973e22434344032" diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index dbc4b2b91d..dc50422026 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -33,6 +33,7 @@ httpx = "^0.24.1" pytest-asyncio = "^0.21.0" pytest-cov = "^4.1.0" ruff = "^0.0.272" +pytest-mock = "^3.11.1" [[tool.poetry.source]] name = "pytorch-cpu" @@ -60,10 +61,14 @@ warn_untyped_fields = true [[tool.mypy.overrides]] module = [ + "huggingface_hub", "transformers.pipelines", "cv2", - "insightface.app", + "insightface.model_zoo", + "insightface.utils.face_align", + "insightface.utils.storage", "sentence_transformers", + "sentence_transformers.util", "aiocache.backends.memory", "aiocache.lock", "aiocache.plugins"