mirror of
https://github.com/immich-app/immich.git
synced 2024-11-24 08:52:28 +02:00
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
This commit is contained in:
parent
2f26a7edae
commit
c73832bd9c
@ -20,7 +20,7 @@ class Settings(BaseSettings):
|
|||||||
min_face_score: float = 0.7
|
min_face_score: float = 0.7
|
||||||
test_full: bool = False
|
test_full: bool = False
|
||||||
|
|
||||||
class Config(BaseSettings.Config):
|
class Config:
|
||||||
env_prefix = "MACHINE_LEARNING_"
|
env_prefix = "MACHINE_LEARNING_"
|
||||||
case_sensitive = False
|
case_sensitive = False
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from types import SimpleNamespace
|
from typing import Iterator, TypeAlias
|
||||||
from typing import Any, Iterator, TypeAlias
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import numpy as np
|
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
|
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
|
@pytest.fixture
|
||||||
def mock_get_model() -> Iterator[mock.Mock]:
|
def mock_get_model() -> Iterator[mock.Mock]:
|
||||||
with mock.patch("app.models.cache.InferenceModel.from_model_type", autospec=True) as mocked:
|
with mock.patch("app.models.cache.InferenceModel.from_model_type", autospec=True) as mocked:
|
||||||
|
@ -9,7 +9,6 @@ from fastapi import Body, Depends, FastAPI
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .config import settings
|
from .config import settings
|
||||||
from .models.base import InferenceModel
|
|
||||||
from .models.cache import ModelCache
|
from .models.cache import ModelCache
|
||||||
from .schemas import (
|
from .schemas import (
|
||||||
EmbeddingResponse,
|
EmbeddingResponse,
|
||||||
@ -38,10 +37,7 @@ async def load_models() -> None:
|
|||||||
|
|
||||||
# Get all models
|
# Get all models
|
||||||
for model_name, model_type in models:
|
for model_name, model_type in models:
|
||||||
if settings.eager_startup:
|
await app.state.model_cache.get(model_name, model_type, eager=settings.eager_startup)
|
||||||
await app.state.model_cache.get(model_name, model_type)
|
|
||||||
else:
|
|
||||||
InferenceModel.from_model_type(model_type, model_name)
|
|
||||||
|
|
||||||
|
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
|
@ -14,22 +14,43 @@ from ..schemas import ModelType
|
|||||||
class InferenceModel(ABC):
|
class InferenceModel(ABC):
|
||||||
_model_type: ModelType
|
_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.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)
|
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:
|
try:
|
||||||
self.load(**model_kwargs)
|
loader(**model_kwargs)
|
||||||
except (OSError, InvalidProtobuf):
|
except (OSError, InvalidProtobuf):
|
||||||
self.clear_cache()
|
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
|
@abstractmethod
|
||||||
def load(self, **model_kwargs: Any) -> None:
|
def _predict(self, inputs: Any) -> Any:
|
||||||
...
|
...
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def predict(self, inputs: Any) -> Any:
|
def _download(self, **model_kwargs: Any) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _load(self, **model_kwargs: Any) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -44,6 +65,10 @@ class InferenceModel(ABC):
|
|||||||
def cache_dir(self, cache_dir: Path) -> None:
|
def cache_dir(self, cache_dir: Path) -> None:
|
||||||
self._cache_dir = cache_dir
|
self._cache_dir = cache_dir
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cached(self) -> bool:
|
||||||
|
return self.cache_dir.exists() and any(self.cache_dir.iterdir())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_model_type(cls, model_type: ModelType, model_name: str, **model_kwargs: Any) -> InferenceModel:
|
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__()}
|
subclasses = {subclass._model_type: subclass for subclass in cls.__subclasses__()}
|
||||||
@ -55,7 +80,11 @@ class InferenceModel(ABC):
|
|||||||
def clear_cache(self) -> None:
|
def clear_cache(self) -> None:
|
||||||
if not self.cache_dir.exists():
|
if not self.cache_dir.exists():
|
||||||
return
|
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.")
|
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)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from PIL.Image import Image
|
from PIL.Image import Image
|
||||||
from sentence_transformers import SentenceTransformer
|
from sentence_transformers import SentenceTransformer
|
||||||
|
from sentence_transformers.util import snapshot_download
|
||||||
|
|
||||||
from ..schemas import ModelType
|
from ..schemas import ModelType
|
||||||
from .base import InferenceModel
|
from .base import InferenceModel
|
||||||
@ -11,12 +11,21 @@ from .base import InferenceModel
|
|||||||
class CLIPSTEncoder(InferenceModel):
|
class CLIPSTEncoder(InferenceModel):
|
||||||
_model_type = ModelType.CLIP
|
_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 = SentenceTransformer(
|
||||||
self.model_name,
|
self.model_name,
|
||||||
cache_folder=self.cache_dir.as_posix(),
|
cache_folder=self.cache_dir.as_posix(),
|
||||||
**model_kwargs,
|
**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()
|
return self.model.encode(image_or_text).tolist()
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import cv2
|
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 ..config import settings
|
||||||
from ..schemas import ModelType
|
from ..schemas import ModelType
|
||||||
@ -22,39 +26,62 @@ class FaceRecognizer(InferenceModel):
|
|||||||
self.min_score = min_score
|
self.min_score = min_score
|
||||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
super().__init__(model_name, cache_dir, **model_kwargs)
|
||||||
|
|
||||||
def load(self, **model_kwargs: Any) -> None:
|
def _download(self, **model_kwargs: Any) -> None:
|
||||||
self.model = FaceAnalysis(
|
zip_file = self.cache_dir / f"{self.model_name}.zip"
|
||||||
name=self.model_name,
|
download_file(f"{BASE_REPO_URL}/{self.model_name}.zip", zip_file)
|
||||||
root=self.cache_dir.as_posix(),
|
with zipfile.ZipFile(zip_file, "r") as zip:
|
||||||
allowed_modules=["detection", "recognition"],
|
members = zip.namelist()
|
||||||
**model_kwargs,
|
det_file = next(model for model in members if model.startswith("det_"))
|
||||||
)
|
rec_file = next(model for model in members if model.startswith("w600k_"))
|
||||||
self.model.prepare(
|
zip.extractall(self.cache_dir, members=[det_file, rec_file])
|
||||||
ctx_id=0,
|
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_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 = []
|
results = []
|
||||||
faces = self.model.get(image)
|
height, width, _ = image.shape
|
||||||
|
for (x1, y1, x2, y2), score, kps in zip(bboxes, scores, kpss):
|
||||||
for face in faces:
|
cropped_img = norm_crop(image, kps)
|
||||||
x1, y1, x2, y2 = face.bbox
|
embedding = self.rec_model.get_feat(cropped_img)[0].tolist()
|
||||||
|
|
||||||
results.append(
|
results.append(
|
||||||
{
|
{
|
||||||
"imageWidth": width,
|
"imageWidth": width,
|
||||||
"imageHeight": height,
|
"imageHeight": height,
|
||||||
"boundingBox": {
|
"boundingBox": {
|
||||||
"x1": round(x1),
|
"x1": x1,
|
||||||
"y1": round(y1),
|
"y1": y1,
|
||||||
"x2": round(x2),
|
"x2": x2,
|
||||||
"y2": round(y2),
|
"y2": y2,
|
||||||
},
|
},
|
||||||
"score": face.det_score.item(),
|
"score": score,
|
||||||
"embedding": face.normed_embedding.tolist(),
|
"embedding": embedding,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cached(self) -> bool:
|
||||||
|
return self.cache_dir.is_dir() and any(self.cache_dir.glob("*.onnx"))
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from huggingface_hub import snapshot_download
|
||||||
from PIL.Image import Image
|
from PIL.Image import Image
|
||||||
from transformers.pipelines import pipeline
|
from transformers.pipelines import pipeline
|
||||||
|
|
||||||
@ -22,14 +23,19 @@ class ImageClassifier(InferenceModel):
|
|||||||
self.min_score = min_score
|
self.min_score = min_score
|
||||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
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 = pipeline(
|
||||||
self.model_type.value,
|
self.model_type.value,
|
||||||
self.model_name,
|
self.model_name,
|
||||||
model_kwargs={"cache_dir": self.cache_dir, **model_kwargs},
|
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
|
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]
|
tags = [tag for pred in predictions for tag in pred["label"].split(", ") if pred["score"] >= self.min_score]
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from typing import TypeAlias
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
|
import numpy as np
|
||||||
import pytest
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from .config import settings
|
from .config import settings
|
||||||
from .models.cache import ModelCache
|
from .models.cache import ModelCache
|
||||||
@ -14,22 +16,43 @@ from .models.facial_recognition import FaceRecognizer
|
|||||||
from .models.image_classification import ImageClassifier
|
from .models.image_classification import ImageClassifier
|
||||||
from .schemas import ModelType
|
from .schemas import ModelType
|
||||||
|
|
||||||
|
ndarray: TypeAlias = np.ndarray[int, np.dtype[np.float32]]
|
||||||
|
|
||||||
|
|
||||||
class TestImageClassifier:
|
class TestImageClassifier:
|
||||||
def test_init(self, mock_classifier_pipeline: mock.Mock) -> None:
|
classifier_preds = [
|
||||||
cache_dir = Path("test_cache")
|
{"label": "that's an image alright", "score": 0.8},
|
||||||
classifier = ImageClassifier("test_model_name", 0.5, cache_dir=cache_dir)
|
{"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
|
def test_eager_init(self, mocker: MockerFixture) -> None:
|
||||||
mock_classifier_pipeline.assert_called_once_with(
|
mocker.patch.object(ImageClassifier, "download")
|
||||||
"image-classification",
|
mock_load = mocker.patch.object(ImageClassifier, "load")
|
||||||
"test_model_name",
|
classifier = ImageClassifier("test_model_name", cache_dir="test_cache", eager=True, test_arg="test_arg")
|
||||||
model_kwargs={"cache_dir": cache_dir},
|
|
||||||
)
|
|
||||||
|
|
||||||
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 = 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)
|
all_labels = classifier.predict(pil_image)
|
||||||
classifier.min_score = 0.5
|
classifier.min_score = 0.5
|
||||||
filtered_labels = classifier.predict(pil_image)
|
filtered_labels = classifier.predict(pil_image)
|
||||||
@ -46,45 +69,94 @@ class TestImageClassifier:
|
|||||||
|
|
||||||
|
|
||||||
class TestCLIP:
|
class TestCLIP:
|
||||||
def test_init(self, mock_st: mock.Mock) -> None:
|
embedding = np.random.rand(512).astype(np.float32)
|
||||||
CLIPSTEncoder("test_model_name", cache_dir="test_cache")
|
|
||||||
|
|
||||||
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 = 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)
|
embedding = clip_encoder.predict(pil_image)
|
||||||
|
|
||||||
assert isinstance(embedding, list)
|
assert isinstance(embedding, list)
|
||||||
assert len(embedding) == 512
|
assert len(embedding) == 512
|
||||||
assert all([isinstance(num, float) for num in embedding])
|
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 = 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")
|
embedding = clip_encoder.predict("test search query")
|
||||||
|
|
||||||
assert isinstance(embedding, list)
|
assert isinstance(embedding, list)
|
||||||
assert len(embedding) == 512
|
assert len(embedding) == 512
|
||||||
assert all([isinstance(num, float) for num in embedding])
|
assert all([isinstance(num, float) for num in embedding])
|
||||||
mock_st.assert_called_once()
|
clip_encoder.model.encode.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
class TestFaceRecognition:
|
class TestFaceRecognition:
|
||||||
def test_init(self, mock_faceanalysis: mock.Mock) -> None:
|
def test_eager_init(self, mocker: MockerFixture) -> None:
|
||||||
FaceRecognizer("test_model_name", cache_dir="test_cache")
|
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(
|
assert face_model.model_name == "test_model_name"
|
||||||
name="test_model_name",
|
mock_load.assert_called_once_with(test_arg="test_arg")
|
||||||
root="test_cache",
|
|
||||||
allowed_modules=["detection", "recognition"],
|
|
||||||
)
|
|
||||||
|
|
||||||
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")
|
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)
|
faces = face_recognizer.predict(cv_image)
|
||||||
|
|
||||||
assert len(faces) == 2
|
assert len(faces) == num_faces
|
||||||
for face in faces:
|
for face in faces:
|
||||||
assert face["imageHeight"] == 800
|
assert face["imageHeight"] == 800
|
||||||
assert face["imageWidth"] == 600
|
assert face["imageWidth"] == 600
|
||||||
@ -92,7 +164,8 @@ class TestFaceRecognition:
|
|||||||
assert len(face["embedding"]) == 512
|
assert len(face["embedding"]) == 512
|
||||||
assert all([isinstance(num, float) for num in face["embedding"]])
|
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
|
@pytest.mark.asyncio
|
||||||
|
245
machine-learning/poetry.lock
generated
245
machine-learning/poetry.lock
generated
@ -421,13 +421,13 @@ cron = ["capturer (>=2.4)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "configargparse"
|
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."
|
description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=3.5"
|
||||||
files = [
|
files = [
|
||||||
{file = "ConfigArgParse-1.5.5-py3-none-any.whl", hash = "sha256:541360ddc1b15c517f95c0d02d1fca4591266628f3667acdc5d13dccc78884ca"},
|
{file = "ConfigArgParse-1.7-py3-none-any.whl", hash = "sha256:d249da6591465c6c26df64a9f73d2536e743be2f244eb3ebe61114af2f94f86b"},
|
||||||
{file = "ConfigArgParse-1.5.5.tar.gz", hash = "sha256:363d80a6d35614bd446e2f2b1b216f3b33741d03ac6d0a92803306f40e555b58"},
|
{file = "ConfigArgParse-1.7.tar.gz", hash = "sha256:e7067471884de5478c58a511e529f0f9bd1c66bfef1dea90935438d6c23306d1"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@ -750,45 +750,45 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fonttools"
|
name = "fonttools"
|
||||||
version = "4.41.1"
|
version = "4.42.0"
|
||||||
description = "Tools to manipulate font files"
|
description = "Tools to manipulate font files"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "fonttools-4.41.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a7bbb290d13c6dd718ec2c3db46fe6c5f6811e7ea1e07f145fd8468176398224"},
|
{file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9c456d1f23deff64ffc8b5b098718e149279abdea4d8692dba69172fb6a0d597"},
|
||||||
{file = "fonttools-4.41.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec453a45778524f925a8f20fd26a3326f398bfc55d534e37bab470c5e415caa1"},
|
{file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:150122ed93127a26bc3670ebab7e2add1e0983d30927733aec327ebf4255b072"},
|
||||||
{file = "fonttools-4.41.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2071267deaa6d93cb16288613419679c77220543551cbe61da02c93d92df72f"},
|
{file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48e82d776d2e93f88ca56567509d102266e7ab2fb707a0326f032fe657335238"},
|
||||||
{file = "fonttools-4.41.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e3334d51f0e37e2c6056e67141b2adabc92613a968797e2571ca8a03bd64773"},
|
{file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58c1165f9b2662645de9b19a8c8bdd636b36294ccc07e1b0163856b74f10bafc"},
|
||||||
{file = "fonttools-4.41.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cac73bbef7734e78c60949da11c4903ee5837168e58772371bd42a75872f4f82"},
|
{file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d6dc3fa91414ff4daa195c05f946e6a575bd214821e26d17ca50f74b35b0fe4"},
|
||||||
{file = "fonttools-4.41.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:edee0900cf0eedb29d17c7876102d6e5a91ee333882b1f5abc83e85b934cadb5"},
|
{file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fae4e801b774cc62cecf4a57b1eae4097903fced00c608d9e2bc8f84cd87b54a"},
|
||||||
{file = "fonttools-4.41.1-cp310-cp310-win32.whl", hash = "sha256:2a22b2c425c698dcd5d6b0ff0b566e8e9663172118db6fd5f1941f9b8063da9b"},
|
{file = "fonttools-4.42.0-cp310-cp310-win32.whl", hash = "sha256:b8600ae7dce6ec3ddfb201abb98c9d53abbf8064d7ac0c8a0d8925e722ccf2a0"},
|
||||||
{file = "fonttools-4.41.1-cp310-cp310-win_amd64.whl", hash = "sha256:547ab36a799dded58a46fa647266c24d0ed43a66028cd1cd4370b246ad426cac"},
|
{file = "fonttools-4.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:57b68eab183fafac7cd7d464a7bfa0fcd4edf6c67837d14fb09c1c20516cf20b"},
|
||||||
{file = "fonttools-4.41.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:849ec722bbf7d3501a0e879e57dec1fc54919d31bff3f690af30bb87970f9784"},
|
{file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0a1466713e54bdbf5521f2f73eebfe727a528905ff5ec63cda40961b4b1eea95"},
|
||||||
{file = "fonttools-4.41.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38cdecd8f1fd4bf4daae7fed1b3170dfc1b523388d6664b2204b351820aa78a7"},
|
{file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3fb2a69870bfe143ec20b039a1c8009e149dd7780dd89554cc8a11f79e5de86b"},
|
||||||
{file = "fonttools-4.41.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ae64303ba670f8959fdaaa30ba0c2dabe75364fdec1caeee596c45d51ca3425"},
|
{file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae881e484702efdb6cf756462622de81d4414c454edfd950b137e9a7352b3cb9"},
|
||||||
{file = "fonttools-4.41.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14f3ccea4cc7dd1b277385adf3c3bf18f9860f87eab9c2fb650b0af16800f55"},
|
{file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27ec3246a088555629f9f0902f7412220c67340553ca91eb540cf247aacb1983"},
|
||||||
{file = "fonttools-4.41.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:33191f062549e6bb1a4782c22a04ebd37009c09360e2d6686ac5083774d06d95"},
|
{file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ece1886d12bb36c48c00b2031518877f41abae317e3a55620d38e307d799b7e"},
|
||||||
{file = "fonttools-4.41.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:704bccd69b0abb6fab9f5e4d2b75896afa48b427caa2c7988792a2ffce35b441"},
|
{file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10dac980f2b975ef74532e2a94bb00e97a95b4595fb7f98db493c474d5f54d0e"},
|
||||||
{file = "fonttools-4.41.1-cp311-cp311-win32.whl", hash = "sha256:4edc795533421e98f60acee7d28fc8d941ff5ac10f44668c9c3635ad72ae9045"},
|
{file = "fonttools-4.42.0-cp311-cp311-win32.whl", hash = "sha256:83b98be5d291e08501bd4fc0c4e0f8e6e05b99f3924068b17c5c9972af6fff84"},
|
||||||
{file = "fonttools-4.41.1-cp311-cp311-win_amd64.whl", hash = "sha256:aaaef294d8e411f0ecb778a0aefd11bb5884c9b8333cc1011bdaf3b58ca4bd75"},
|
{file = "fonttools-4.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:e35bed436726194c5e6e094fdfb423fb7afaa0211199f9d245e59e11118c576c"},
|
||||||
{file = "fonttools-4.41.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3d1f9471134affc1e3b1b806db6e3e2ad3fa99439e332f1881a474c825101096"},
|
{file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c36c904ce0322df01e590ba814d5d69e084e985d7e4c2869378671d79662a7d4"},
|
||||||
{file = "fonttools-4.41.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:59eba8b2e749a1de85760da22333f3d17c42b66e03758855a12a2a542723c6e7"},
|
{file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d54e600a2bcfa5cdaa860237765c01804a03b08404d6affcd92942fa7315ffba"},
|
||||||
{file = "fonttools-4.41.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9b3cc10dc9e0834b6665fd63ae0c6964c6bc3d7166e9bc84772e0edd09f9fa2"},
|
{file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01cfe02416b6d416c5c8d15e30315cbcd3e97d1b50d3b34b0ce59f742ef55258"},
|
||||||
{file = "fonttools-4.41.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2c2964bdc827ba6b8a91dc6de792620be4da3922c4cf0599f36a488c07e2b2"},
|
{file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f81ed9065b4bd3f4f3ce8e4873cd6a6b3f4e92b1eddefde35d332c6f414acc3"},
|
||||||
{file = "fonttools-4.41.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7763316111df7b5165529f4183a334aa24c13cdb5375ffa1dc8ce309c8bf4e5c"},
|
{file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:685a4dd6cf31593b50d6d441feb7781a4a7ef61e19551463e14ed7c527b86f9f"},
|
||||||
{file = "fonttools-4.41.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b2d1ee95be42b80d1f002d1ee0a51d7a435ea90d36f1a5ae331be9962ee5a3f1"},
|
{file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:329341ba3d86a36e482610db56b30705384cb23bd595eac8cbb045f627778e9d"},
|
||||||
{file = "fonttools-4.41.1-cp38-cp38-win32.whl", hash = "sha256:f48602c0b3fd79cd83a34c40af565fe6db7ac9085c8823b552e6e751e3a5b8be"},
|
{file = "fonttools-4.42.0-cp38-cp38-win32.whl", hash = "sha256:4655c480a1a4d706152ff54f20e20cf7609084016f1df3851cce67cef768f40a"},
|
||||||
{file = "fonttools-4.41.1-cp38-cp38-win_amd64.whl", hash = "sha256:b0938ebbeccf7c80bb9a15e31645cf831572c3a33d5cc69abe436e7000c61b14"},
|
{file = "fonttools-4.42.0-cp38-cp38-win_amd64.whl", hash = "sha256:6bd7e4777bff1dcb7c4eff4786998422770f3bfbef8be401c5332895517ba3fa"},
|
||||||
{file = "fonttools-4.41.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e5c2b0a95a221838991e2f0e455dec1ca3a8cc9cd54febd68cc64d40fdb83669"},
|
{file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9b55d2a3b360e0c7fc5bd8badf1503ca1c11dd3a1cd20f2c26787ffa145a9c7"},
|
||||||
{file = "fonttools-4.41.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:891cfc5a83b0307688f78b9bb446f03a7a1ad981690ac8362f50518bc6153975"},
|
{file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0df8ef75ba5791e873c9eac2262196497525e3f07699a2576d3ab9ddf41cb619"},
|
||||||
{file = "fonttools-4.41.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73ef0bb5d60eb02ba4d3a7d23ada32184bd86007cb2de3657cfcb1175325fc83"},
|
{file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd2363ea7728496827658682d049ffb2e98525e2247ca64554864a8cc945568"},
|
||||||
{file = "fonttools-4.41.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f240d9adf0583ac8fc1646afe7f4ac039022b6f8fa4f1575a2cfa53675360b69"},
|
{file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d40673b2e927f7cd0819c6f04489dfbeb337b4a7b10fc633c89bf4f34ecb9620"},
|
||||||
{file = "fonttools-4.41.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bdd729744ae7ecd7f7311ad25d99da4999003dcfe43b436cf3c333d4e68de73d"},
|
{file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c8bf88f9e3ce347c716921804ef3a8330cb128284eb6c0b6c4b3574f3c580023"},
|
||||||
{file = "fonttools-4.41.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b927e5f466d99c03e6e20961946314b81d6e3490d95865ef88061144d9f62e38"},
|
{file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:703101eb0490fae32baf385385d47787b73d9ea55253df43b487c89ec767e0d7"},
|
||||||
{file = "fonttools-4.41.1-cp39-cp39-win32.whl", hash = "sha256:afce2aeb80be72b4da7dd114f10f04873ff512793d13ce0b19d12b2a4c44c0f0"},
|
{file = "fonttools-4.42.0-cp39-cp39-win32.whl", hash = "sha256:f0290ea7f9945174bd4dfd66e96149037441eb2008f3649094f056201d99e293"},
|
||||||
{file = "fonttools-4.41.1-cp39-cp39-win_amd64.whl", hash = "sha256:1df1b6f4c7c4bc8201eb47f3b268adbf2539943aa43c400f84556557e3e109c0"},
|
{file = "fonttools-4.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:ae7df0ae9ee2f3f7676b0ff6f4ebe48ad0acaeeeaa0b6839d15dbf0709f2c5ef"},
|
||||||
{file = "fonttools-4.41.1-py3-none-any.whl", hash = "sha256:952cb405f78734cf6466252fec42e206450d1a6715746013f64df9cbd4f896fa"},
|
{file = "fonttools-4.42.0-py3-none-any.whl", hash = "sha256:dfe7fa7e607f7e8b58d0c32501a3a7cac148538300626d1b930082c90ae7f6bd"},
|
||||||
{file = "fonttools-4.41.1.tar.gz", hash = "sha256:e16a9449f21a93909c5be2f5ed5246420f2316e94195dbfccb5238aaa38f9751"},
|
{file = "fonttools-4.42.0.tar.gz", hash = "sha256:614b1283dca88effd20ee48160518e6de275ce9b5456a3134d5f235523fc5065"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@ -1525,13 +1525,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "locust"
|
name = "locust"
|
||||||
version = "2.15.1"
|
version = "2.16.1"
|
||||||
description = "Developer friendly load testing framework"
|
description = "Developer friendly load testing framework"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "locust-2.15.1-py3-none-any.whl", hash = "sha256:9e0bb30b4962f9c9611174df0fdea2a4e3f41656b36dc7b0a1a46f618a83d5a9"},
|
{file = "locust-2.16.1-py3-none-any.whl", hash = "sha256:d0f01f9fca6a7d9be987b32185799d9e219fce3b9a3b8250ea03e88003335804"},
|
||||||
{file = "locust-2.15.1.tar.gz", hash = "sha256:a6307f3bf995c180f66e7caed94360b8c8ed95d64dca508614d803d5b0b39f15"},
|
{file = "locust-2.16.1.tar.gz", hash = "sha256:cd54f179b679ae927e9b3ffd2b6a7c89c1078103cfbe96b4dd53c7872774b619"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -1860,36 +1860,36 @@ twitter = ["twython"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numpy"
|
name = "numpy"
|
||||||
version = "1.25.1"
|
version = "1.25.2"
|
||||||
description = "Fundamental package for array computing in Python"
|
description = "Fundamental package for array computing in Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "numpy-1.25.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77d339465dff3eb33c701430bcb9c325b60354698340229e1dff97745e6b3efa"},
|
{file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"},
|
||||||
{file = "numpy-1.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d736b75c3f2cb96843a5c7f8d8ccc414768d34b0a75f466c05f3a739b406f10b"},
|
{file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"},
|
||||||
{file = "numpy-1.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a90725800caeaa160732d6b31f3f843ebd45d6b5f3eec9e8cc287e30f2805bf"},
|
{file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"},
|
||||||
{file = "numpy-1.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c6c9261d21e617c6dc5eacba35cb68ec36bb72adcff0dee63f8fbc899362588"},
|
{file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"},
|
||||||
{file = "numpy-1.25.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0def91f8af6ec4bb94c370e38c575855bf1d0be8a8fbfba42ef9c073faf2cf19"},
|
{file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"},
|
||||||
{file = "numpy-1.25.1-cp310-cp310-win32.whl", hash = "sha256:fd67b306320dcadea700a8f79b9e671e607f8696e98ec255915c0c6d6b818503"},
|
{file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"},
|
||||||
{file = "numpy-1.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:c1516db588987450b85595586605742879e50dcce923e8973f79529651545b57"},
|
{file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"},
|
||||||
{file = "numpy-1.25.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b82655dd8efeea69dbf85d00fca40013d7f503212bc5259056244961268b66e"},
|
{file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"},
|
||||||
{file = "numpy-1.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e8f6049c4878cb16960fbbfb22105e49d13d752d4d8371b55110941fb3b17800"},
|
{file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"},
|
||||||
{file = "numpy-1.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41a56b70e8139884eccb2f733c2f7378af06c82304959e174f8e7370af112e09"},
|
{file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"},
|
||||||
{file = "numpy-1.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5154b1a25ec796b1aee12ac1b22f414f94752c5f94832f14d8d6c9ac40bcca6"},
|
{file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"},
|
||||||
{file = "numpy-1.25.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38eb6548bb91c421261b4805dc44def9ca1a6eef6444ce35ad1669c0f1a3fc5d"},
|
{file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"},
|
||||||
{file = "numpy-1.25.1-cp311-cp311-win32.whl", hash = "sha256:791f409064d0a69dd20579345d852c59822c6aa087f23b07b1b4e28ff5880fcb"},
|
{file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"},
|
||||||
{file = "numpy-1.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:c40571fe966393b212689aa17e32ed905924120737194b5d5c1b20b9ed0fb171"},
|
{file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"},
|
||||||
{file = "numpy-1.25.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3d7abcdd85aea3e6cdddb59af2350c7ab1ed764397f8eec97a038ad244d2d105"},
|
{file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"},
|
||||||
{file = "numpy-1.25.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a180429394f81c7933634ae49b37b472d343cccb5bb0c4a575ac8bbc433722f"},
|
{file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"},
|
||||||
{file = "numpy-1.25.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d412c1697c3853c6fc3cb9751b4915859c7afe6a277c2bf00acf287d56c4e625"},
|
{file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"},
|
||||||
{file = "numpy-1.25.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20e1266411120a4f16fad8efa8e0454d21d00b8c7cee5b5ccad7565d95eb42dd"},
|
{file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"},
|
||||||
{file = "numpy-1.25.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f76aebc3358ade9eacf9bc2bb8ae589863a4f911611694103af05346637df1b7"},
|
{file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"},
|
||||||
{file = "numpy-1.25.1-cp39-cp39-win32.whl", hash = "sha256:247d3ffdd7775bdf191f848be8d49100495114c82c2bd134e8d5d075fb386a1c"},
|
{file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"},
|
||||||
{file = "numpy-1.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:1d5d3c68e443c90b38fdf8ef40e60e2538a27548b39b12b73132456847f4b631"},
|
{file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"},
|
||||||
{file = "numpy-1.25.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:35a9527c977b924042170a0887de727cd84ff179e478481404c5dc66b4170009"},
|
{file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"},
|
||||||
{file = "numpy-1.25.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d3fe3dd0506a28493d82dc3cf254be8cd0d26f4008a417385cbf1ae95b54004"},
|
{file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"},
|
||||||
{file = "numpy-1.25.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:012097b5b0d00a11070e8f2e261128c44157a8689f7dedcf35576e525893f4fe"},
|
{file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"},
|
||||||
{file = "numpy-1.25.1.tar.gz", hash = "sha256:9a3a9f3a61480cc086117b426a8bd86869c213fc4072e606f01c4e4b66eb92bf"},
|
{file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2020,13 +2020,13 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathspec"
|
name = "pathspec"
|
||||||
version = "0.11.1"
|
version = "0.11.2"
|
||||||
description = "Utility library for gitignore style pattern matching of file paths."
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
|
{file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"},
|
||||||
{file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
|
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2110,18 +2110,18 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platformdirs"
|
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\"."
|
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"},
|
{file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"},
|
||||||
{file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"},
|
{file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
|
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.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"]
|
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pluggy"
|
name = "pluggy"
|
||||||
@ -2215,47 +2215,47 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "1.10.11"
|
version = "1.10.12"
|
||||||
description = "Data validation and settings management using python type hints"
|
description = "Data validation and settings management using python type hints"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"},
|
{file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"},
|
||||||
{file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"},
|
{file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"},
|
||||||
{file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"},
|
{file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"},
|
||||||
{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.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"},
|
||||||
{file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"},
|
{file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"},
|
||||||
{file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"},
|
{file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"},
|
||||||
{file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"},
|
{file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"},
|
||||||
{file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"},
|
{file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"},
|
||||||
{file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"},
|
{file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"},
|
||||||
{file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"},
|
{file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"},
|
||||||
{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.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"},
|
||||||
{file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"},
|
{file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"},
|
||||||
{file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"},
|
{file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"},
|
||||||
{file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"},
|
{file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"},
|
||||||
{file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"},
|
{file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"},
|
||||||
{file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"},
|
{file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"},
|
||||||
{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.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"},
|
||||||
{file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"},
|
{file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"},
|
||||||
{file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"},
|
{file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"},
|
||||||
{file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"},
|
{file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"},
|
||||||
{file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"},
|
{file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"},
|
||||||
{file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"},
|
{file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"},
|
||||||
{file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"},
|
{file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"},
|
||||||
{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.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"},
|
||||||
{file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"},
|
{file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"},
|
||||||
{file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"},
|
{file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"},
|
||||||
{file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"},
|
{file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"},
|
||||||
{file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"},
|
{file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"},
|
||||||
{file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"},
|
{file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"},
|
||||||
{file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"},
|
{file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"},
|
||||||
{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.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"},
|
||||||
{file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"},
|
{file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"},
|
||||||
{file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"},
|
{file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"},
|
||||||
{file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"},
|
{file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"},
|
||||||
{file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"},
|
{file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"},
|
||||||
{file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"},
|
{file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -2346,6 +2346,23 @@ pytest = ">=4.6"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
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]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "python-dateutil"
|
||||||
version = "2.8.2"
|
version = "2.8.2"
|
||||||
@ -3664,4 +3681,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "4a06d26614d016bfdbb290ad93b3c71378ad03b249a8f06cb53c82465862977f"
|
content-hash = "0a4f26164e0dd32ce9d63da9322739c0812e56a5bdfb4148c973e22434344032"
|
||||||
|
@ -33,6 +33,7 @@ httpx = "^0.24.1"
|
|||||||
pytest-asyncio = "^0.21.0"
|
pytest-asyncio = "^0.21.0"
|
||||||
pytest-cov = "^4.1.0"
|
pytest-cov = "^4.1.0"
|
||||||
ruff = "^0.0.272"
|
ruff = "^0.0.272"
|
||||||
|
pytest-mock = "^3.11.1"
|
||||||
|
|
||||||
[[tool.poetry.source]]
|
[[tool.poetry.source]]
|
||||||
name = "pytorch-cpu"
|
name = "pytorch-cpu"
|
||||||
@ -60,10 +61,14 @@ warn_untyped_fields = true
|
|||||||
|
|
||||||
[[tool.mypy.overrides]]
|
[[tool.mypy.overrides]]
|
||||||
module = [
|
module = [
|
||||||
|
"huggingface_hub",
|
||||||
"transformers.pipelines",
|
"transformers.pipelines",
|
||||||
"cv2",
|
"cv2",
|
||||||
"insightface.app",
|
"insightface.model_zoo",
|
||||||
|
"insightface.utils.face_align",
|
||||||
|
"insightface.utils.storage",
|
||||||
"sentence_transformers",
|
"sentence_transformers",
|
||||||
|
"sentence_transformers.util",
|
||||||
"aiocache.backends.memory",
|
"aiocache.backends.memory",
|
||||||
"aiocache.lock",
|
"aiocache.lock",
|
||||||
"aiocache.plugins"
|
"aiocache.plugins"
|
||||||
|
Loading…
Reference in New Issue
Block a user