diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2db37ae852..5672316cf5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -121,6 +121,36 @@ jobs: working-directory: ./mobile 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: name: Check generated files are up-to-date runs-on: ubuntu-latest diff --git a/machine-learning/app/config.py b/machine-learning/app/config.py index 3845cf1f35..70520b27ca 100644 --- a/machine-learning/app/config.py +++ b/machine-learning/app/config.py @@ -18,6 +18,7 @@ class Settings(BaseSettings): port: int = 3003 workers: int = 1 min_face_score: float = 0.7 + test_full: bool = False class Config(BaseSettings.Config): env_prefix = "MACHINE_LEARNING_" diff --git a/machine-learning/app/conftest.py b/machine-learning/app/conftest.py new file mode 100644 index 0000000000..aa73049ecc --- /dev/null +++ b/machine-learning/app/conftest.py @@ -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) diff --git a/machine-learning/app/main.py b/machine-learning/app/main.py index e59d0d8382..35ee27204c 100644 --- a/machine-learning/app/main.py +++ b/machine-learning/app/main.py @@ -24,9 +24,11 @@ from .schemas import ( app = FastAPI() -@app.on_event("startup") -async def startup_event() -> None: +def init_state() -> None: app.state.model_cache = ModelCache(ttl=settings.model_ttl, revalidate=True) + + +async def load_models() -> None: models = [ (settings.classification_model, ModelType.IMAGE_CLASSIFICATION), (settings.clip_image_model, ModelType.CLIP), @@ -42,6 +44,12 @@ async def startup_event() -> None: 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: return Image.open(BytesIO(byte_image)) @@ -69,9 +77,7 @@ def ping() -> str: async def image_classification( image: Image.Image = Depends(dep_pil_image), ) -> list[str]: - model = await app.state.model_cache.get( - settings.classification_model, ModelType.IMAGE_CLASSIFICATION - ) + model = await app.state.model_cache.get(settings.classification_model, ModelType.IMAGE_CLASSIFICATION) labels = model.predict(image) return labels @@ -108,9 +114,7 @@ async def clip_encode_text(payload: TextModelRequest) -> list[float]: async def facial_recognition( image: cv2.Mat = Depends(dep_cv_image), ) -> list[dict[str, Any]]: - model = await app.state.model_cache.get( - settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION - ) + model = await app.state.model_cache.get(settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION) faces = model.predict(image) return faces diff --git a/machine-learning/app/models/base.py b/machine-learning/app/models/base.py index a62d7730c3..98d6fb8349 100644 --- a/machine-learning/app/models/base.py +++ b/machine-learning/app/models/base.py @@ -5,7 +5,7 @@ from pathlib import Path from shutil import rmtree 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 ..schemas import ModelType @@ -14,15 +14,9 @@ from ..schemas import ModelType class InferenceModel(ABC): _model_type: ModelType - def __init__( - self, model_name: str, cache_dir: Path | None = None, **model_kwargs - ) -> None: + def __init__(self, model_name: str, cache_dir: Path | str | None = None, **model_kwargs: Any) -> None: self.model_name = model_name - self._cache_dir = ( - 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) try: self.load(**model_kwargs) @@ -51,12 +45,8 @@ class InferenceModel(ABC): self._cache_dir = cache_dir @classmethod - def from_model_type( - cls, model_type: ModelType, model_name, **model_kwargs - ) -> InferenceModel: - subclasses = { - subclass._model_type: subclass for subclass in cls.__subclasses__() - } + 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__()} if model_type not in subclasses: raise ValueError(f"Unsupported model type: {model_type}") @@ -66,8 +56,6 @@ class InferenceModel(ABC): if not self.cache_dir.exists(): return elif 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) diff --git a/machine-learning/app/models/cache.py b/machine-learning/app/models/cache.py index bcf959fe7e..086a57c5ae 100644 --- a/machine-learning/app/models/cache.py +++ b/machine-learning/app/models/cache.py @@ -1,4 +1,5 @@ import asyncio +from typing import Any from aiocache.backends.memory import SimpleMemoryCache from aiocache.lock import OptimisticLock @@ -34,13 +35,9 @@ class ModelCache: if profiling: plugins.append(TimingPlugin()) - self.cache = SimpleMemoryCache( - ttl=ttl, timeout=timeout, plugins=plugins, namespace=None - ) + self.cache = SimpleMemoryCache(ttl=ttl, timeout=timeout, plugins=plugins, namespace=None) - async def get( - self, model_name: str, model_type: ModelType, **model_kwargs - ) -> InferenceModel: + async def get(self, model_name: str, model_type: ModelType, **model_kwargs: Any) -> InferenceModel: """ Args: 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: model = await asyncio.get_running_loop().run_in_executor( None, - lambda: InferenceModel.from_model_type( - model_type, model_name, **model_kwargs - ), + lambda: InferenceModel.from_model_type(model_type, model_name, **model_kwargs), ) await lock.cas(model, ttl=self.ttl) return model @@ -73,7 +68,14 @@ class ModelCache: class RevalidationPlugin(BasePlugin): """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: return if namespace is not None: @@ -81,7 +83,14 @@ class RevalidationPlugin(BasePlugin): if key in client._handlers: 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: return diff --git a/machine-learning/app/models/facial_recognition.py b/machine-learning/app/models/facial_recognition.py index 99349409f0..b9f96b7b44 100644 --- a/machine-learning/app/models/facial_recognition.py +++ b/machine-learning/app/models/facial_recognition.py @@ -16,8 +16,8 @@ class FaceRecognizer(InferenceModel): self, model_name: str, min_score: float = settings.min_face_score, - cache_dir: Path | None = None, - **model_kwargs, + cache_dir: Path | str | None = None, + **model_kwargs: Any, ) -> None: self.min_score = min_score super().__init__(model_name, cache_dir, **model_kwargs) diff --git a/machine-learning/app/models/image_classification.py b/machine-learning/app/models/image_classification.py index 9f7e4cfb6c..0b5887f53f 100644 --- a/machine-learning/app/models/image_classification.py +++ b/machine-learning/app/models/image_classification.py @@ -16,8 +16,8 @@ class ImageClassifier(InferenceModel): self, model_name: str, min_score: float = settings.min_tag_score, - cache_dir: Path | None = None, - **model_kwargs, + cache_dir: Path | str | None = None, + **model_kwargs: Any, ) -> None: self.min_score = min_score super().__init__(model_name, cache_dir, **model_kwargs) @@ -30,13 +30,7 @@ class ImageClassifier(InferenceModel): ) def predict(self, image: Image) -> list[str]: - predictions = self.model(image) - tags = list( - { - tag - for pred in predictions - for tag in pred["label"].split(", ") - if pred["score"] >= self.min_score - } - ) + 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] + return tags diff --git a/machine-learning/app/schemas.py b/machine-learning/app/schemas.py index 16618faa68..b19b498739 100644 --- a/machine-learning/app/schemas.py +++ b/machine-learning/app/schemas.py @@ -4,10 +4,7 @@ from pydantic import BaseModel def to_lower_camel(string: str) -> str: - tokens = [ - token.capitalize() if i > 0 else token - for i, token in enumerate(string.split("_")) - ] + tokens = [token.capitalize() if i > 0 else token for i, token in enumerate(string.split("_"))] return "".join(tokens) diff --git a/machine-learning/app/test_main.py b/machine-learning/app/test_main.py new file mode 100644 index 0000000000..11a0466c83 --- /dev/null +++ b/machine-learning/app/test_main.py @@ -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 diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock index c829e02b68..ea6a955f88 100644 --- a/machine-learning/poetry.lock +++ b/machine-learning/poetry.lock @@ -424,13 +424,13 @@ cron = ["capturer (>=2.4)"] [[package]] 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." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "ConfigArgParse-1.5.3-py3-none-any.whl", hash = "sha256:18f6535a2db9f6e02bd5626cc7455eac3e96b9ab3d969d366f9aafd5c5c00fe7"}, - {file = "ConfigArgParse-1.5.3.tar.gz", hash = "sha256:1b0b3cbf664ab59dada57123c81eff3d9737e0d11d8cf79e3d6eb10823f1739f"}, + {file = "ConfigArgParse-1.5.5-py3-none-any.whl", hash = "sha256:541360ddc1b15c517f95c0d02d1fca4591266628f3667acdc5d13dccc78884ca"}, + {file = "ConfigArgParse-1.5.5.tar.gz", hash = "sha256:363d80a6d35614bd446e2f2b1b216f3b33741d03ac6d0a92803306f40e555b58"}, ] [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-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]] name = "cycler" version = "0.11.0" @@ -639,18 +711,17 @@ Flask = "*" [[package]] name = "flask-cors" -version = "3.0.10" +version = "4.0.0" description = "A Flask extension adding a decorator for CORS support" optional = false python-versions = "*" files = [ - {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"}, - {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"}, + {file = "Flask-Cors-4.0.0.tar.gz", hash = "sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"}, + {file = "Flask_Cors-4.0.0-py2.py3-none-any.whl", hash = "sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783"}, ] [package.dependencies] Flask = ">=0.9" -Six = "*" [[package]] name = "flatbuffers" @@ -1039,6 +1110,27 @@ files = [ {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]] name = "httptools" version = "0.5.0" @@ -1092,6 +1184,29 @@ files = [ [package.extras] 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]] name = "huggingface-hub" version = "0.15.1" @@ -1584,42 +1699,42 @@ files = [ [[package]] name = "mypy" -version = "1.4.0" +version = "1.4.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3af348e0925a59213244f28c7c0c3a2c2088b4ba2fe9d6c8d4fbb0aba0b7d05"}, - {file = "mypy-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0b2e0da7ff9dd8d2066d093d35a169305fc4e38db378281fce096768a3dbdbf"}, - {file = "mypy-1.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210fe0f39ec5be45dd9d0de253cb79245f0a6f27631d62e0c9c7988be7152965"}, - {file = "mypy-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f7a5971490fd4a5a436e143105a1f78fa8b3fe95b30fff2a77542b4f3227a01f"}, - {file = "mypy-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:50f65f0e9985f1e50040e603baebab83efed9eb37e15a22a4246fa7cd660f981"}, - {file = "mypy-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1b5c875fcf3e7217a3de7f708166f641ca154b589664c44a6fd6d9f17d9e7e"}, - {file = "mypy-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4c734d947e761c7ceb1f09a98359dd5666460acbc39f7d0a6b6beec373c5840"}, - {file = "mypy-1.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5984a8d13d35624e3b235a793c814433d810acba9eeefe665cdfed3d08bc3af"}, - {file = "mypy-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0f98973e39e4a98709546a9afd82e1ffcc50c6ec9ce6f7870f33ebbf0bd4f26d"}, - {file = "mypy-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:19d42b08c7532d736a7e0fb29525855e355fa51fd6aef4f9bbc80749ff64b1a2"}, - {file = "mypy-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ba9a69172abaa73910643744d3848877d6aac4a20c41742027dcfd8d78f05d9"}, - {file = "mypy-1.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a34eed094c16cad0f6b0d889811592c7a9b7acf10d10a7356349e325d8704b4f"}, - {file = "mypy-1.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:53c2a1fed81e05ded10a4557fe12bae05b9ecf9153f162c662a71d924d504135"}, - {file = "mypy-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bba57b4d2328740749f676807fcf3036e9de723530781405cc5a5e41fc6e20de"}, - {file = "mypy-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:653863c75f0dbb687d92eb0d4bd9fe7047d096987ecac93bb7b1bc336de48ebd"}, - {file = "mypy-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7461469e163f87a087a5e7aa224102a30f037c11a096a0ceeb721cb0dce274c8"}, - {file = "mypy-1.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cf0ca95e4b8adeaf07815a78b4096b65adf64ea7871b39a2116c19497fcd0dd"}, - {file = "mypy-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:94a81b9354545123feb1a99b960faeff9e1fa204fce47e0042335b473d71530d"}, - {file = "mypy-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:67242d5b28ed0fa88edd8f880aed24da481929467fdbca6487167cb5e3fd31ff"}, - {file = "mypy-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f2b353eebef669529d9bd5ae3566905a685ae98b3af3aad7476d0d519714758"}, - {file = "mypy-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62bf18d97c6b089f77f0067b4e321db089d8520cdeefc6ae3ec0f873621c22e5"}, - {file = "mypy-1.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca33ab70a4aaa75bb01086a0b04f0ba8441e51e06fc57e28585176b08cad533b"}, - {file = "mypy-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5a0ee54c2cb0f957f8a6f41794d68f1a7e32b9968675ade5846f538504856d42"}, - {file = "mypy-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6c34d43e3d54ad05024576aef28081d9d0580f6fa7f131255f54020eb12f5352"}, - {file = "mypy-1.4.0-py3-none-any.whl", hash = "sha256:f051ca656be0c179c735a4c3193f307d34c92fdc4908d44fd4516fbe8b10567d"}, - {file = "mypy-1.4.0.tar.gz", hash = "sha256:de1e7e68148a213036276d1f5303b3836ad9a774188961eb2684eddff593b042"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, + {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, + {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, + {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, + {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, + {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, + {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, + {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, + {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, + {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, + {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, + {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, + {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, + {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, + {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, + {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, + {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, + {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, + {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" -typing-extensions = ">=3.10" +typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -2133,6 +2248,42 @@ pluggy = ">=0.12,<2.0" [package.extras] 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]] name = "python-dateutil" version = "2.8.2" @@ -2504,6 +2655,32 @@ files = [ {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]] name = "safetensors" version = "0.3.1" @@ -3425,4 +3602,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "2981003c319d9990f05abec1e3d02dc1ea6680b0bf1590376c5e47801311d89f" +content-hash = "e0ac37404f0c11ee5b478d2c7113986a2d40d02e2b985ff18846374a65025a26" diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index eb2a18f1fb..58f3c8ef8f 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -22,6 +22,8 @@ fastapi = "^0.95.2" uvicorn = {extras = ["standard"], version = "^0.22.0"} pydantic = "^1.10.8" aiocache = "^0.12.1" +pytest-cov = "^4.1.0" +ruff = "^0.0.272" [tool.poetry.group.dev.dependencies] mypy = "^1.3.0" @@ -29,6 +31,8 @@ black = "^23.3.0" pytest = "^7.3.1" locust = "^2.15.1" gunicorn = "^20.1.0" +httpx = "^0.24.1" +pytest-asyncio = "^0.21.0" [[tool.poetry.source]] name = "pytorch-cpu" @@ -39,9 +43,6 @@ priority = "explicit" requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" -[tool.flake8] -max-line-length = 120 - [tool.mypy] python_version = "3.11" plugins = "pydantic.mypy" @@ -49,11 +50,35 @@ follow_imports = "silent" warn_redundant_casts = true disallow_any_generics = true check_untyped_defs = true -no_implicit_reexport = true disallow_untyped_defs = true [tool.pydantic-mypy] init_forbid_extra = true init_typed = true warn_required_dynamic_aliases = true -warn_untyped_fields = true \ No newline at end of file +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']