mirror of
https://github.com/immich-app/immich.git
synced 2024-11-24 08:52:28 +02:00
chore(ml): added testing and github workflow (#2969)
* added testing * github action for python, made mypy happy * formatted with black * minor fixes and styling * test model cache * cache test dependencies * narrowed model cache tests * moved endpoint tests to their own class * cleaned up fixtures * formatting * removed unused dep
This commit is contained in:
parent
5e3bdc76b2
commit
df1e8679d9
30
.github/workflows/test.yml
vendored
30
.github/workflows/test.yml
vendored
@ -121,6 +121,36 @@ jobs:
|
|||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
run: flutter test -j 1
|
run: flutter test -j 1
|
||||||
|
|
||||||
|
ml-unit-tests:
|
||||||
|
name: Run ML unit tests and checks
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./machine-learning
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Install poetry
|
||||||
|
run: pipx install poetry
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: 3.11
|
||||||
|
cache: "poetry"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
poetry install --with dev
|
||||||
|
- name: Lint with ruff
|
||||||
|
run: |
|
||||||
|
poetry run ruff check --format=github app
|
||||||
|
- name: Check black formatting
|
||||||
|
run: |
|
||||||
|
poetry run black --check app
|
||||||
|
- name: Run mypy type checking
|
||||||
|
run: |
|
||||||
|
poetry run mypy --install-types --non-interactive app/
|
||||||
|
- name: Run tests and coverage
|
||||||
|
run: |
|
||||||
|
poetry run pytest --cov app
|
||||||
|
|
||||||
generated-api-up-to-date:
|
generated-api-up-to-date:
|
||||||
name: Check generated files are up-to-date
|
name: Check generated files are up-to-date
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -18,6 +18,7 @@ class Settings(BaseSettings):
|
|||||||
port: int = 3003
|
port: int = 3003
|
||||||
workers: int = 1
|
workers: int = 1
|
||||||
min_face_score: float = 0.7
|
min_face_score: float = 0.7
|
||||||
|
test_full: bool = False
|
||||||
|
|
||||||
class Config(BaseSettings.Config):
|
class Config(BaseSettings.Config):
|
||||||
env_prefix = "MACHINE_LEARNING_"
|
env_prefix = "MACHINE_LEARNING_"
|
||||||
|
119
machine-learning/app/conftest.py
Normal file
119
machine-learning/app/conftest.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
from types import SimpleNamespace
|
||||||
|
from typing import Any, Iterator, TypeAlias
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from .main import app, init_state
|
||||||
|
|
||||||
|
ndarray: TypeAlias = np.ndarray[int, np.dtype[np.float32]]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def pil_image() -> Image.Image:
|
||||||
|
return Image.new("RGB", (600, 800))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
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:
|
||||||
|
yield mocked
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def deployed_app() -> TestClient:
|
||||||
|
init_state()
|
||||||
|
return TestClient(app)
|
@ -24,9 +24,11 @@ from .schemas import (
|
|||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
@app.on_event("startup")
|
def init_state() -> None:
|
||||||
async def startup_event() -> None:
|
|
||||||
app.state.model_cache = ModelCache(ttl=settings.model_ttl, revalidate=True)
|
app.state.model_cache = ModelCache(ttl=settings.model_ttl, revalidate=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def load_models() -> None:
|
||||||
models = [
|
models = [
|
||||||
(settings.classification_model, ModelType.IMAGE_CLASSIFICATION),
|
(settings.classification_model, ModelType.IMAGE_CLASSIFICATION),
|
||||||
(settings.clip_image_model, ModelType.CLIP),
|
(settings.clip_image_model, ModelType.CLIP),
|
||||||
@ -42,6 +44,12 @@ async def startup_event() -> None:
|
|||||||
InferenceModel.from_model_type(model_type, model_name)
|
InferenceModel.from_model_type(model_type, model_name)
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
async def startup_event() -> None:
|
||||||
|
init_state()
|
||||||
|
await load_models()
|
||||||
|
|
||||||
|
|
||||||
def dep_pil_image(byte_image: bytes = Body(...)) -> Image.Image:
|
def dep_pil_image(byte_image: bytes = Body(...)) -> Image.Image:
|
||||||
return Image.open(BytesIO(byte_image))
|
return Image.open(BytesIO(byte_image))
|
||||||
|
|
||||||
@ -69,9 +77,7 @@ def ping() -> str:
|
|||||||
async def image_classification(
|
async def image_classification(
|
||||||
image: Image.Image = Depends(dep_pil_image),
|
image: Image.Image = Depends(dep_pil_image),
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
model = await app.state.model_cache.get(
|
model = await app.state.model_cache.get(settings.classification_model, ModelType.IMAGE_CLASSIFICATION)
|
||||||
settings.classification_model, ModelType.IMAGE_CLASSIFICATION
|
|
||||||
)
|
|
||||||
labels = model.predict(image)
|
labels = model.predict(image)
|
||||||
return labels
|
return labels
|
||||||
|
|
||||||
@ -108,9 +114,7 @@ async def clip_encode_text(payload: TextModelRequest) -> list[float]:
|
|||||||
async def facial_recognition(
|
async def facial_recognition(
|
||||||
image: cv2.Mat = Depends(dep_cv_image),
|
image: cv2.Mat = Depends(dep_cv_image),
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
model = await app.state.model_cache.get(
|
model = await app.state.model_cache.get(settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION)
|
||||||
settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION
|
|
||||||
)
|
|
||||||
faces = model.predict(image)
|
faces = model.predict(image)
|
||||||
return faces
|
return faces
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from pathlib import Path
|
|||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf
|
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf # type: ignore
|
||||||
|
|
||||||
from ..config import get_cache_dir
|
from ..config import get_cache_dir
|
||||||
from ..schemas import ModelType
|
from ..schemas import ModelType
|
||||||
@ -14,15 +14,9 @@ from ..schemas import ModelType
|
|||||||
class InferenceModel(ABC):
|
class InferenceModel(ABC):
|
||||||
_model_type: ModelType
|
_model_type: ModelType
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, model_name: str, cache_dir: Path | str | None = None, **model_kwargs: Any) -> None:
|
||||||
self, model_name: str, cache_dir: Path | None = None, **model_kwargs
|
|
||||||
) -> None:
|
|
||||||
self.model_name = model_name
|
self.model_name = model_name
|
||||||
self._cache_dir = (
|
self._cache_dir = Path(cache_dir) if cache_dir is not None else get_cache_dir(model_name, self.model_type)
|
||||||
cache_dir
|
|
||||||
if cache_dir is not None
|
|
||||||
else get_cache_dir(model_name, self.model_type)
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.load(**model_kwargs)
|
self.load(**model_kwargs)
|
||||||
@ -51,12 +45,8 @@ class InferenceModel(ABC):
|
|||||||
self._cache_dir = cache_dir
|
self._cache_dir = cache_dir
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_model_type(
|
def from_model_type(cls, model_type: ModelType, model_name: str, **model_kwargs: Any) -> InferenceModel:
|
||||||
cls, model_type: ModelType, model_name, **model_kwargs
|
subclasses = {subclass._model_type: subclass for subclass in cls.__subclasses__()}
|
||||||
) -> InferenceModel:
|
|
||||||
subclasses = {
|
|
||||||
subclass._model_type: subclass for subclass in cls.__subclasses__()
|
|
||||||
}
|
|
||||||
if model_type not in subclasses:
|
if model_type not in subclasses:
|
||||||
raise ValueError(f"Unsupported model type: {model_type}")
|
raise ValueError(f"Unsupported model type: {model_type}")
|
||||||
|
|
||||||
@ -66,8 +56,6 @@ class InferenceModel(ABC):
|
|||||||
if not self.cache_dir.exists():
|
if not self.cache_dir.exists():
|
||||||
return
|
return
|
||||||
elif not rmtree.avoids_symlink_attacks:
|
elif not rmtree.avoids_symlink_attacks:
|
||||||
raise RuntimeError(
|
raise RuntimeError("Attempted to clear cache, but rmtree is not safe on this platform.")
|
||||||
"Attempted to clear cache, but rmtree is not safe on this platform."
|
|
||||||
)
|
|
||||||
|
|
||||||
rmtree(self.cache_dir)
|
rmtree(self.cache_dir)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from aiocache.backends.memory import SimpleMemoryCache
|
from aiocache.backends.memory import SimpleMemoryCache
|
||||||
from aiocache.lock import OptimisticLock
|
from aiocache.lock import OptimisticLock
|
||||||
@ -34,13 +35,9 @@ class ModelCache:
|
|||||||
if profiling:
|
if profiling:
|
||||||
plugins.append(TimingPlugin())
|
plugins.append(TimingPlugin())
|
||||||
|
|
||||||
self.cache = SimpleMemoryCache(
|
self.cache = SimpleMemoryCache(ttl=ttl, timeout=timeout, plugins=plugins, namespace=None)
|
||||||
ttl=ttl, timeout=timeout, plugins=plugins, namespace=None
|
|
||||||
)
|
|
||||||
|
|
||||||
async def get(
|
async def get(self, model_name: str, model_type: ModelType, **model_kwargs: Any) -> InferenceModel:
|
||||||
self, model_name: str, model_type: ModelType, **model_kwargs
|
|
||||||
) -> InferenceModel:
|
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
model_name: Name of model in the model hub used for the task.
|
model_name: Name of model in the model hub used for the task.
|
||||||
@ -56,9 +53,7 @@ class ModelCache:
|
|||||||
async with OptimisticLock(self.cache, key) as lock:
|
async with OptimisticLock(self.cache, key) as lock:
|
||||||
model = await asyncio.get_running_loop().run_in_executor(
|
model = await asyncio.get_running_loop().run_in_executor(
|
||||||
None,
|
None,
|
||||||
lambda: InferenceModel.from_model_type(
|
lambda: InferenceModel.from_model_type(model_type, model_name, **model_kwargs),
|
||||||
model_type, model_name, **model_kwargs
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
await lock.cas(model, ttl=self.ttl)
|
await lock.cas(model, ttl=self.ttl)
|
||||||
return model
|
return model
|
||||||
@ -73,7 +68,14 @@ class ModelCache:
|
|||||||
class RevalidationPlugin(BasePlugin):
|
class RevalidationPlugin(BasePlugin):
|
||||||
"""Revalidates cache item's TTL after cache hit."""
|
"""Revalidates cache item's TTL after cache hit."""
|
||||||
|
|
||||||
async def post_get(self, client, key, ret=None, namespace=None, **kwargs):
|
async def post_get(
|
||||||
|
self,
|
||||||
|
client: SimpleMemoryCache,
|
||||||
|
key: str,
|
||||||
|
ret: Any | None = None,
|
||||||
|
namespace: str | None = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
if ret is None:
|
if ret is None:
|
||||||
return
|
return
|
||||||
if namespace is not None:
|
if namespace is not None:
|
||||||
@ -81,7 +83,14 @@ class RevalidationPlugin(BasePlugin):
|
|||||||
if key in client._handlers:
|
if key in client._handlers:
|
||||||
await client.expire(key, client.ttl)
|
await client.expire(key, client.ttl)
|
||||||
|
|
||||||
async def post_multi_get(self, client, keys, ret=None, namespace=None, **kwargs):
|
async def post_multi_get(
|
||||||
|
self,
|
||||||
|
client: SimpleMemoryCache,
|
||||||
|
keys: list[str],
|
||||||
|
ret: list[Any] | None = None,
|
||||||
|
namespace: str | None = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
if ret is None:
|
if ret is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ class FaceRecognizer(InferenceModel):
|
|||||||
self,
|
self,
|
||||||
model_name: str,
|
model_name: str,
|
||||||
min_score: float = settings.min_face_score,
|
min_score: float = settings.min_face_score,
|
||||||
cache_dir: Path | None = None,
|
cache_dir: Path | str | None = None,
|
||||||
**model_kwargs,
|
**model_kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
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)
|
||||||
|
@ -16,8 +16,8 @@ class ImageClassifier(InferenceModel):
|
|||||||
self,
|
self,
|
||||||
model_name: str,
|
model_name: str,
|
||||||
min_score: float = settings.min_tag_score,
|
min_score: float = settings.min_tag_score,
|
||||||
cache_dir: Path | None = None,
|
cache_dir: Path | str | None = None,
|
||||||
**model_kwargs,
|
**model_kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
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)
|
||||||
@ -30,13 +30,7 @@ class ImageClassifier(InferenceModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def predict(self, image: Image) -> list[str]:
|
def predict(self, image: Image) -> list[str]:
|
||||||
predictions = self.model(image)
|
predictions: list[dict[str, Any]] = self.model(image) # type: ignore
|
||||||
tags = list(
|
tags = [tag for pred in predictions for tag in pred["label"].split(", ") if pred["score"] >= self.min_score]
|
||||||
{
|
|
||||||
tag
|
|
||||||
for pred in predictions
|
|
||||||
for tag in pred["label"].split(", ")
|
|
||||||
if pred["score"] >= self.min_score
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return tags
|
return tags
|
||||||
|
@ -4,10 +4,7 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
|
|
||||||
def to_lower_camel(string: str) -> str:
|
def to_lower_camel(string: str) -> str:
|
||||||
tokens = [
|
tokens = [token.capitalize() if i > 0 else token for i, token in enumerate(string.split("_"))]
|
||||||
token.capitalize() if i > 0 else token
|
|
||||||
for i, token in enumerate(string.split("_"))
|
|
||||||
]
|
|
||||||
return "".join(tokens)
|
return "".join(tokens)
|
||||||
|
|
||||||
|
|
||||||
|
183
machine-learning/app/test_main.py
Normal file
183
machine-learning/app/test_main.py
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import pytest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from .config import settings
|
||||||
|
from .models.cache import ModelCache
|
||||||
|
from .models.clip import CLIPSTEncoder
|
||||||
|
from .models.facial_recognition import FaceRecognizer
|
||||||
|
from .models.image_classification import ImageClassifier
|
||||||
|
from .schemas import ModelType
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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_min_score(self, pil_image: Image.Image, mock_classifier_pipeline: mock.Mock) -> None:
|
||||||
|
classifier = ImageClassifier("test_model_name", min_score=0.0)
|
||||||
|
classifier.min_score = 0.0
|
||||||
|
all_labels = classifier.predict(pil_image)
|
||||||
|
classifier.min_score = 0.5
|
||||||
|
filtered_labels = classifier.predict(pil_image)
|
||||||
|
|
||||||
|
assert all_labels == [
|
||||||
|
"that's an image alright",
|
||||||
|
"well it ends with .jpg",
|
||||||
|
"idk",
|
||||||
|
"im just seeing bytes",
|
||||||
|
"not sure",
|
||||||
|
"probably a virus",
|
||||||
|
]
|
||||||
|
assert filtered_labels == ["that's an image alright"]
|
||||||
|
|
||||||
|
|
||||||
|
class TestCLIP:
|
||||||
|
def test_init(self, mock_st: mock.Mock) -> None:
|
||||||
|
CLIPSTEncoder("test_model_name", cache_dir="test_cache")
|
||||||
|
|
||||||
|
mock_st.assert_called_once_with("test_model_name", cache_folder="test_cache")
|
||||||
|
|
||||||
|
def test_basic_image(self, pil_image: Image.Image, mock_st: mock.Mock) -> None:
|
||||||
|
clip_encoder = CLIPSTEncoder("test_model_name", cache_dir="test_cache")
|
||||||
|
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()
|
||||||
|
|
||||||
|
def test_basic_text(self, mock_st: mock.Mock) -> None:
|
||||||
|
clip_encoder = CLIPSTEncoder("test_model_name", cache_dir="test_cache")
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
class TestFaceRecognition:
|
||||||
|
def test_init(self, mock_faceanalysis: mock.Mock) -> None:
|
||||||
|
FaceRecognizer("test_model_name", cache_dir="test_cache")
|
||||||
|
|
||||||
|
mock_faceanalysis.assert_called_once_with(
|
||||||
|
name="test_model_name",
|
||||||
|
root="test_cache",
|
||||||
|
allowed_modules=["detection", "recognition"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_basic(self, cv_image: cv2.Mat, mock_faceanalysis: mock.Mock) -> None:
|
||||||
|
face_recognizer = FaceRecognizer("test_model_name", min_score=0.0, cache_dir="test_cache")
|
||||||
|
faces = face_recognizer.predict(cv_image)
|
||||||
|
|
||||||
|
assert len(faces) == 2
|
||||||
|
for face in faces:
|
||||||
|
assert face["imageHeight"] == 800
|
||||||
|
assert face["imageWidth"] == 600
|
||||||
|
assert isinstance(face["embedding"], list)
|
||||||
|
assert len(face["embedding"]) == 512
|
||||||
|
assert all([isinstance(num, float) for num in face["embedding"]])
|
||||||
|
|
||||||
|
mock_faceanalysis.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
class TestCache:
|
||||||
|
async def test_caches(self, mock_get_model: mock.Mock) -> None:
|
||||||
|
model_cache = ModelCache()
|
||||||
|
await model_cache.get("test_model_name", ModelType.IMAGE_CLASSIFICATION)
|
||||||
|
await model_cache.get("test_model_name", ModelType.IMAGE_CLASSIFICATION)
|
||||||
|
assert len(model_cache.cache._cache) == 1
|
||||||
|
mock_get_model.assert_called_once()
|
||||||
|
|
||||||
|
async def test_kwargs_used(self, mock_get_model: mock.Mock) -> None:
|
||||||
|
model_cache = ModelCache()
|
||||||
|
await model_cache.get("test_model_name", ModelType.IMAGE_CLASSIFICATION, cache_dir="test_cache")
|
||||||
|
mock_get_model.assert_called_once_with(
|
||||||
|
ModelType.IMAGE_CLASSIFICATION, "test_model_name", cache_dir="test_cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def test_different_clip(self, mock_get_model: mock.Mock) -> None:
|
||||||
|
model_cache = ModelCache()
|
||||||
|
await model_cache.get("test_image_model_name", ModelType.CLIP)
|
||||||
|
await model_cache.get("test_text_model_name", ModelType.CLIP)
|
||||||
|
mock_get_model.assert_has_calls(
|
||||||
|
[
|
||||||
|
mock.call(ModelType.CLIP, "test_image_model_name"),
|
||||||
|
mock.call(ModelType.CLIP, "test_text_model_name"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert len(model_cache.cache._cache) == 2
|
||||||
|
|
||||||
|
@mock.patch("app.models.cache.OptimisticLock", autospec=True)
|
||||||
|
async def test_model_ttl(self, mock_lock_cls: mock.Mock, mock_get_model: mock.Mock) -> None:
|
||||||
|
model_cache = ModelCache(ttl=100)
|
||||||
|
await model_cache.get("test_model_name", ModelType.IMAGE_CLASSIFICATION)
|
||||||
|
mock_lock_cls.return_value.__aenter__.return_value.cas.assert_called_with(mock.ANY, ttl=100)
|
||||||
|
|
||||||
|
@mock.patch("app.models.cache.SimpleMemoryCache.expire")
|
||||||
|
async def test_revalidate(self, mock_cache_expire: mock.Mock, mock_get_model: mock.Mock) -> None:
|
||||||
|
model_cache = ModelCache(ttl=100, revalidate=True)
|
||||||
|
await model_cache.get("test_model_name", ModelType.IMAGE_CLASSIFICATION)
|
||||||
|
await model_cache.get("test_model_name", ModelType.IMAGE_CLASSIFICATION)
|
||||||
|
mock_cache_expire.assert_called_once_with(mock.ANY, 100)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
not settings.test_full,
|
||||||
|
reason="More time-consuming since it deploys the app and loads models.",
|
||||||
|
)
|
||||||
|
class TestEndpoints:
|
||||||
|
def test_tagging_endpoint(self, pil_image: Image.Image, deployed_app: TestClient) -> None:
|
||||||
|
byte_image = BytesIO()
|
||||||
|
pil_image.save(byte_image, format="jpeg")
|
||||||
|
headers = {"Content-Type": "image/jpg"}
|
||||||
|
response = deployed_app.post(
|
||||||
|
"http://localhost:3003/image-classifier/tag-image",
|
||||||
|
content=byte_image.getvalue(),
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
def test_clip_image_endpoint(self, pil_image: Image.Image, deployed_app: TestClient) -> None:
|
||||||
|
byte_image = BytesIO()
|
||||||
|
pil_image.save(byte_image, format="jpeg")
|
||||||
|
headers = {"Content-Type": "image/jpg"}
|
||||||
|
response = deployed_app.post(
|
||||||
|
"http://localhost:3003/sentence-transformer/encode-image",
|
||||||
|
content=byte_image.getvalue(),
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
def test_clip_text_endpoint(self, deployed_app: TestClient) -> None:
|
||||||
|
response = deployed_app.post(
|
||||||
|
"http://localhost:3003/sentence-transformer/encode-text",
|
||||||
|
json={"text": "test search query"},
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
def test_face_endpoint(self, pil_image: Image.Image, deployed_app: TestClient) -> None:
|
||||||
|
byte_image = BytesIO()
|
||||||
|
pil_image.save(byte_image, format="jpeg")
|
||||||
|
headers = {"Content-Type": "image/jpg"}
|
||||||
|
response = deployed_app.post(
|
||||||
|
"http://localhost:3003/facial-recognition/detect-faces",
|
||||||
|
content=byte_image.getvalue(),
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
249
machine-learning/poetry.lock
generated
249
machine-learning/poetry.lock
generated
@ -424,13 +424,13 @@ cron = ["capturer (>=2.4)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "configargparse"
|
name = "configargparse"
|
||||||
version = "1.5.3"
|
version = "1.5.5"
|
||||||
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 = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
files = [
|
files = [
|
||||||
{file = "ConfigArgParse-1.5.3-py3-none-any.whl", hash = "sha256:18f6535a2db9f6e02bd5626cc7455eac3e96b9ab3d969d366f9aafd5c5c00fe7"},
|
{file = "ConfigArgParse-1.5.5-py3-none-any.whl", hash = "sha256:541360ddc1b15c517f95c0d02d1fca4591266628f3667acdc5d13dccc78884ca"},
|
||||||
{file = "ConfigArgParse-1.5.3.tar.gz", hash = "sha256:1b0b3cbf664ab59dada57123c81eff3d9737e0d11d8cf79e3d6eb10823f1739f"},
|
{file = "ConfigArgParse-1.5.5.tar.gz", hash = "sha256:363d80a6d35614bd446e2f2b1b216f3b33741d03ac6d0a92803306f40e555b58"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@ -495,6 +495,78 @@ mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.2.0)", "types-Pill
|
|||||||
test = ["Pillow", "contourpy[test-no-images]", "matplotlib"]
|
test = ["Pillow", "contourpy[test-no-images]", "matplotlib"]
|
||||||
test-no-images = ["pytest", "pytest-cov", "wurlitzer"]
|
test-no-images = ["pytest", "pytest-cov", "wurlitzer"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.2.7"
|
||||||
|
description = "Code coverage measurement for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"},
|
||||||
|
{file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"},
|
||||||
|
{file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"},
|
||||||
|
{file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"},
|
||||||
|
{file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"},
|
||||||
|
{file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"},
|
||||||
|
{file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"},
|
||||||
|
{file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"},
|
||||||
|
{file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"},
|
||||||
|
{file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"},
|
||||||
|
{file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"},
|
||||||
|
{file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"},
|
||||||
|
{file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"},
|
||||||
|
{file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"},
|
||||||
|
{file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"},
|
||||||
|
{file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"},
|
||||||
|
{file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"},
|
||||||
|
{file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"},
|
||||||
|
{file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"},
|
||||||
|
{file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"},
|
||||||
|
{file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"},
|
||||||
|
{file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"},
|
||||||
|
{file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"},
|
||||||
|
{file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"},
|
||||||
|
{file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"},
|
||||||
|
{file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"},
|
||||||
|
{file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"},
|
||||||
|
{file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"},
|
||||||
|
{file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"},
|
||||||
|
{file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"},
|
||||||
|
{file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"},
|
||||||
|
{file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"},
|
||||||
|
{file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"},
|
||||||
|
{file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"},
|
||||||
|
{file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"},
|
||||||
|
{file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"},
|
||||||
|
{file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"},
|
||||||
|
{file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"},
|
||||||
|
{file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"},
|
||||||
|
{file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"},
|
||||||
|
{file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"},
|
||||||
|
{file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"},
|
||||||
|
{file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"},
|
||||||
|
{file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"},
|
||||||
|
{file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"},
|
||||||
|
{file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"},
|
||||||
|
{file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"},
|
||||||
|
{file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"},
|
||||||
|
{file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"},
|
||||||
|
{file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"},
|
||||||
|
{file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"},
|
||||||
|
{file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"},
|
||||||
|
{file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"},
|
||||||
|
{file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"},
|
||||||
|
{file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"},
|
||||||
|
{file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"},
|
||||||
|
{file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"},
|
||||||
|
{file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"},
|
||||||
|
{file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"},
|
||||||
|
{file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
toml = ["tomli"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cycler"
|
name = "cycler"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@ -639,18 +711,17 @@ Flask = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flask-cors"
|
name = "flask-cors"
|
||||||
version = "3.0.10"
|
version = "4.0.0"
|
||||||
description = "A Flask extension adding a decorator for CORS support"
|
description = "A Flask extension adding a decorator for CORS support"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"},
|
{file = "Flask-Cors-4.0.0.tar.gz", hash = "sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"},
|
||||||
{file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"},
|
{file = "Flask_Cors-4.0.0-py2.py3-none-any.whl", hash = "sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
Flask = ">=0.9"
|
Flask = ">=0.9"
|
||||||
Six = "*"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flatbuffers"
|
name = "flatbuffers"
|
||||||
@ -1039,6 +1110,27 @@ files = [
|
|||||||
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpcore"
|
||||||
|
version = "0.17.2"
|
||||||
|
description = "A minimal low-level HTTP client."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "httpcore-0.17.2-py3-none-any.whl", hash = "sha256:5581b9c12379c4288fe70f43c710d16060c10080617001e6b22a3b6dbcbefd36"},
|
||||||
|
{file = "httpcore-0.17.2.tar.gz", hash = "sha256:125f8375ab60036db632f34f4b627a9ad085048eef7cb7d2616fea0f739f98af"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
anyio = ">=3.0,<5.0"
|
||||||
|
certifi = "*"
|
||||||
|
h11 = ">=0.13,<0.15"
|
||||||
|
sniffio = "==1.*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
http2 = ["h2 (>=3,<5)"]
|
||||||
|
socks = ["socksio (==1.*)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httptools"
|
name = "httptools"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -1092,6 +1184,29 @@ files = [
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
test = ["Cython (>=0.29.24,<0.30.0)"]
|
test = ["Cython (>=0.29.24,<0.30.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpx"
|
||||||
|
version = "0.24.1"
|
||||||
|
description = "The next generation HTTP client."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"},
|
||||||
|
{file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
certifi = "*"
|
||||||
|
httpcore = ">=0.15.0,<0.18.0"
|
||||||
|
idna = "*"
|
||||||
|
sniffio = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
brotli = ["brotli", "brotlicffi"]
|
||||||
|
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
|
||||||
|
http2 = ["h2 (>=3,<5)"]
|
||||||
|
socks = ["socksio (==1.*)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "huggingface-hub"
|
name = "huggingface-hub"
|
||||||
version = "0.15.1"
|
version = "0.15.1"
|
||||||
@ -1584,42 +1699,42 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "1.4.0"
|
version = "1.4.1"
|
||||||
description = "Optional static typing for Python"
|
description = "Optional static typing for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "mypy-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3af348e0925a59213244f28c7c0c3a2c2088b4ba2fe9d6c8d4fbb0aba0b7d05"},
|
{file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"},
|
||||||
{file = "mypy-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0b2e0da7ff9dd8d2066d093d35a169305fc4e38db378281fce096768a3dbdbf"},
|
{file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"},
|
||||||
{file = "mypy-1.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210fe0f39ec5be45dd9d0de253cb79245f0a6f27631d62e0c9c7988be7152965"},
|
{file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"},
|
||||||
{file = "mypy-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f7a5971490fd4a5a436e143105a1f78fa8b3fe95b30fff2a77542b4f3227a01f"},
|
{file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"},
|
||||||
{file = "mypy-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:50f65f0e9985f1e50040e603baebab83efed9eb37e15a22a4246fa7cd660f981"},
|
{file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"},
|
||||||
{file = "mypy-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1b5c875fcf3e7217a3de7f708166f641ca154b589664c44a6fd6d9f17d9e7e"},
|
{file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"},
|
||||||
{file = "mypy-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4c734d947e761c7ceb1f09a98359dd5666460acbc39f7d0a6b6beec373c5840"},
|
{file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"},
|
||||||
{file = "mypy-1.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5984a8d13d35624e3b235a793c814433d810acba9eeefe665cdfed3d08bc3af"},
|
{file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"},
|
||||||
{file = "mypy-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0f98973e39e4a98709546a9afd82e1ffcc50c6ec9ce6f7870f33ebbf0bd4f26d"},
|
{file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"},
|
||||||
{file = "mypy-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:19d42b08c7532d736a7e0fb29525855e355fa51fd6aef4f9bbc80749ff64b1a2"},
|
{file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"},
|
||||||
{file = "mypy-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ba9a69172abaa73910643744d3848877d6aac4a20c41742027dcfd8d78f05d9"},
|
{file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"},
|
||||||
{file = "mypy-1.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a34eed094c16cad0f6b0d889811592c7a9b7acf10d10a7356349e325d8704b4f"},
|
{file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"},
|
||||||
{file = "mypy-1.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:53c2a1fed81e05ded10a4557fe12bae05b9ecf9153f162c662a71d924d504135"},
|
{file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"},
|
||||||
{file = "mypy-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bba57b4d2328740749f676807fcf3036e9de723530781405cc5a5e41fc6e20de"},
|
{file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"},
|
||||||
{file = "mypy-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:653863c75f0dbb687d92eb0d4bd9fe7047d096987ecac93bb7b1bc336de48ebd"},
|
{file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"},
|
||||||
{file = "mypy-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7461469e163f87a087a5e7aa224102a30f037c11a096a0ceeb721cb0dce274c8"},
|
{file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"},
|
||||||
{file = "mypy-1.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cf0ca95e4b8adeaf07815a78b4096b65adf64ea7871b39a2116c19497fcd0dd"},
|
{file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"},
|
||||||
{file = "mypy-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:94a81b9354545123feb1a99b960faeff9e1fa204fce47e0042335b473d71530d"},
|
{file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"},
|
||||||
{file = "mypy-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:67242d5b28ed0fa88edd8f880aed24da481929467fdbca6487167cb5e3fd31ff"},
|
{file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"},
|
||||||
{file = "mypy-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f2b353eebef669529d9bd5ae3566905a685ae98b3af3aad7476d0d519714758"},
|
{file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"},
|
||||||
{file = "mypy-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62bf18d97c6b089f77f0067b4e321db089d8520cdeefc6ae3ec0f873621c22e5"},
|
{file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"},
|
||||||
{file = "mypy-1.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca33ab70a4aaa75bb01086a0b04f0ba8441e51e06fc57e28585176b08cad533b"},
|
{file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"},
|
||||||
{file = "mypy-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5a0ee54c2cb0f957f8a6f41794d68f1a7e32b9968675ade5846f538504856d42"},
|
{file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"},
|
||||||
{file = "mypy-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6c34d43e3d54ad05024576aef28081d9d0580f6fa7f131255f54020eb12f5352"},
|
{file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"},
|
||||||
{file = "mypy-1.4.0-py3-none-any.whl", hash = "sha256:f051ca656be0c179c735a4c3193f307d34c92fdc4908d44fd4516fbe8b10567d"},
|
{file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"},
|
||||||
{file = "mypy-1.4.0.tar.gz", hash = "sha256:de1e7e68148a213036276d1f5303b3836ad9a774188961eb2684eddff593b042"},
|
{file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
mypy-extensions = ">=1.0.0"
|
mypy-extensions = ">=1.0.0"
|
||||||
typing-extensions = ">=3.10"
|
typing-extensions = ">=4.1.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dmypy = ["psutil (>=4.0)"]
|
dmypy = ["psutil (>=4.0)"]
|
||||||
@ -2133,6 +2248,42 @@ pluggy = ">=0.12,<2.0"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-asyncio"
|
||||||
|
version = "0.21.0"
|
||||||
|
description = "Pytest support for asyncio"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "pytest-asyncio-0.21.0.tar.gz", hash = "sha256:2b38a496aef56f56b0e87557ec313e11e1ab9276fc3863f6a7be0f1d0e415e1b"},
|
||||||
|
{file = "pytest_asyncio-0.21.0-py3-none-any.whl", hash = "sha256:f2b3366b7cd501a4056858bd39349d5af19742aed2d81660b7998b6341c7eb9c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pytest = ">=7.0.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
|
||||||
|
testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "4.1.0"
|
||||||
|
description = "Pytest plugin for measuring coverage."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
|
||||||
|
{file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||||
|
pytest = ">=4.6"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "python-dateutil"
|
||||||
version = "2.8.2"
|
version = "2.8.2"
|
||||||
@ -2504,6 +2655,32 @@ files = [
|
|||||||
{file = "roundrobin-0.0.4.tar.gz", hash = "sha256:7e9d19a5bd6123d99993fb935fa86d25c88bb2096e493885f61737ed0f5e9abd"},
|
{file = "roundrobin-0.0.4.tar.gz", hash = "sha256:7e9d19a5bd6123d99993fb935fa86d25c88bb2096e493885f61737ed0f5e9abd"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff"
|
||||||
|
version = "0.0.272"
|
||||||
|
description = "An extremely fast Python linter, written in Rust."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "ruff-0.0.272-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:ae9b57546e118660175d45d264b87e9b4c19405c75b587b6e4d21e6a17bf4fdf"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:1609b864a8d7ee75a8c07578bdea0a7db75a144404e75ef3162e0042bfdc100d"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee76b4f05fcfff37bd6ac209d1370520d509ea70b5a637bdf0a04d0c99e13dff"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48eccf225615e106341a641f826b15224b8a4240b84269ead62f0afd6d7e2d95"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:677284430ac539bb23421a2b431b4ebc588097ef3ef918d0e0a8d8ed31fea216"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9c4bfb75456a8e1efe14c52fcefb89cfb8f2a0d31ed8d804b82c6cf2dc29c42c"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86bc788245361a8148ff98667da938a01e1606b28a45e50ac977b09d3ad2c538"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b2ea68d2aa69fff1b20b67636b1e3e22a6a39e476c880da1282c3e4bf6ee5a"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd2bbe337a3f84958f796c77820d55ac2db1e6753f39d1d1baed44e07f13f96d"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d5a208f8ef0e51d4746930589f54f9f92f84bb69a7d15b1de34ce80a7681bc00"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:905ff8f3d6206ad56fcd70674453527b9011c8b0dc73ead27618426feff6908e"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-musllinux_1_2_i686.whl", hash = "sha256:19643d448f76b1eb8a764719072e9c885968971bfba872e14e7257e08bc2f2b7"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:691d72a00a99707a4e0b2846690961157aef7b17b6b884f6b4420a9f25cd39b5"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-win32.whl", hash = "sha256:dc406e5d756d932da95f3af082814d2467943631a587339ee65e5a4f4fbe83eb"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-win_amd64.whl", hash = "sha256:a37ec80e238ead2969b746d7d1b6b0d31aa799498e9ba4281ab505b93e1f4b28"},
|
||||||
|
{file = "ruff-0.0.272-py3-none-win_arm64.whl", hash = "sha256:06b8ee4eb8711ab119db51028dd9f5384b44728c23586424fd6e241a5b9c4a3b"},
|
||||||
|
{file = "ruff-0.0.272.tar.gz", hash = "sha256:273a01dc8c3c4fd4c2af7ea7a67c8d39bb09bce466e640dd170034da75d14cab"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "safetensors"
|
name = "safetensors"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -3425,4 +3602,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 = "2981003c319d9990f05abec1e3d02dc1ea6680b0bf1590376c5e47801311d89f"
|
content-hash = "e0ac37404f0c11ee5b478d2c7113986a2d40d02e2b985ff18846374a65025a26"
|
||||||
|
@ -22,6 +22,8 @@ fastapi = "^0.95.2"
|
|||||||
uvicorn = {extras = ["standard"], version = "^0.22.0"}
|
uvicorn = {extras = ["standard"], version = "^0.22.0"}
|
||||||
pydantic = "^1.10.8"
|
pydantic = "^1.10.8"
|
||||||
aiocache = "^0.12.1"
|
aiocache = "^0.12.1"
|
||||||
|
pytest-cov = "^4.1.0"
|
||||||
|
ruff = "^0.0.272"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
mypy = "^1.3.0"
|
mypy = "^1.3.0"
|
||||||
@ -29,6 +31,8 @@ black = "^23.3.0"
|
|||||||
pytest = "^7.3.1"
|
pytest = "^7.3.1"
|
||||||
locust = "^2.15.1"
|
locust = "^2.15.1"
|
||||||
gunicorn = "^20.1.0"
|
gunicorn = "^20.1.0"
|
||||||
|
httpx = "^0.24.1"
|
||||||
|
pytest-asyncio = "^0.21.0"
|
||||||
|
|
||||||
[[tool.poetry.source]]
|
[[tool.poetry.source]]
|
||||||
name = "pytorch-cpu"
|
name = "pytorch-cpu"
|
||||||
@ -39,9 +43,6 @@ priority = "explicit"
|
|||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.flake8]
|
|
||||||
max-line-length = 120
|
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
python_version = "3.11"
|
python_version = "3.11"
|
||||||
plugins = "pydantic.mypy"
|
plugins = "pydantic.mypy"
|
||||||
@ -49,7 +50,6 @@ follow_imports = "silent"
|
|||||||
warn_redundant_casts = true
|
warn_redundant_casts = true
|
||||||
disallow_any_generics = true
|
disallow_any_generics = true
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
no_implicit_reexport = true
|
|
||||||
disallow_untyped_defs = true
|
disallow_untyped_defs = true
|
||||||
|
|
||||||
[tool.pydantic-mypy]
|
[tool.pydantic-mypy]
|
||||||
@ -57,3 +57,28 @@ init_forbid_extra = true
|
|||||||
init_typed = true
|
init_typed = true
|
||||||
warn_required_dynamic_aliases = true
|
warn_required_dynamic_aliases = true
|
||||||
warn_untyped_fields = true
|
warn_untyped_fields = true
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = [
|
||||||
|
"transformers.pipelines",
|
||||||
|
"cv2",
|
||||||
|
"insightface.app",
|
||||||
|
"sentence_transformers",
|
||||||
|
"aiocache.backends.memory",
|
||||||
|
"aiocache.lock",
|
||||||
|
"aiocache.plugins"
|
||||||
|
]
|
||||||
|
ignore_missing_imports = true
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 120
|
||||||
|
target-version = "py311"
|
||||||
|
select = ["E", "F", "I"]
|
||||||
|
ignore = ["F401"]
|
||||||
|
|
||||||
|
[tool.ruff.per-file-ignores]
|
||||||
|
"test_main.py" = ["F403"]
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 120
|
||||||
|
target-version = ['py311']
|
||||||
|
Loading…
Reference in New Issue
Block a user