mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 10:37:11 -04:00 
			
		
		
		
	chore(ml): installable package (#17153)
* app -> immich_ml * fix test ci * omit file name * add new line * add new line
This commit is contained in:
		
							parent
							
								
									f7d730eb05
								
							
						
					
					
						commit
						84c35e35d6
					
				
							
								
								
									
										8
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| @ -395,16 +395,16 @@ jobs: | |||||||
|           uv sync --extra cpu |           uv sync --extra cpu | ||||||
|       - name: Lint with ruff |       - name: Lint with ruff | ||||||
|         run: | |         run: | | ||||||
|           uv run ruff check --output-format=github app |           uv run ruff check --output-format=github immich_ml | ||||||
|       - name: Check black formatting |       - name: Check black formatting | ||||||
|         run: | |         run: | | ||||||
|           uv run black --check app |           uv run black --check immich_ml | ||||||
|       - name: Run mypy type checking |       - name: Run mypy type checking | ||||||
|         run: | |         run: | | ||||||
|           uv run mypy --strict app/ |           uv run mypy --strict immich_ml/ | ||||||
|       - name: Run tests and coverage |       - name: Run tests and coverage | ||||||
|         run: | |         run: | | ||||||
|           uv run pytest app --cov=app --cov-report term-missing |           uv run pytest --cov=immich_ml --cov-report term-missing | ||||||
| 
 | 
 | ||||||
|   github-files-formatting: |   github-files-formatting: | ||||||
|     name: .github Files Formatting |     name: .github Files Formatting | ||||||
|  | |||||||
| @ -51,7 +51,6 @@ ARG DEVICE | |||||||
| ENV PYTHONDONTWRITEBYTECODE=1 \ | ENV PYTHONDONTWRITEBYTECODE=1 \ | ||||||
|     PYTHONUNBUFFERED=1 \ |     PYTHONUNBUFFERED=1 \ | ||||||
|     VIRTUAL_ENV=/opt/venv |     VIRTUAL_ENV=/opt/venv | ||||||
| WORKDIR /usr/src/app |  | ||||||
| 
 | 
 | ||||||
| RUN apt-get update && apt-get install -y --no-install-recommends g++ | RUN apt-get update && apt-get install -y --no-install-recommends g++ | ||||||
| 
 | 
 | ||||||
| @ -66,6 +65,8 @@ RUN if [ "$DEVICE" = "rocm" ]; then \ | |||||||
| 
 | 
 | ||||||
| FROM python:3.11-slim-bookworm@sha256:7029b00486ac40bed03e36775b864d3f3d39dcbdf19cd45e6a52d541e6c178f0 AS prod-cpu | FROM python:3.11-slim-bookworm@sha256:7029b00486ac40bed03e36775b864d3f3d39dcbdf19cd45e6a52d541e6c178f0 AS prod-cpu | ||||||
| 
 | 
 | ||||||
|  | ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 | ||||||
|  | 
 | ||||||
| FROM prod-cpu AS prod-openvino | FROM prod-cpu AS prod-openvino | ||||||
| 
 | 
 | ||||||
| RUN apt-get update && \ | RUN apt-get update && \ | ||||||
| @ -94,7 +95,8 @@ FROM rocm/dev-ubuntu-22.04:6.3.4-complete@sha256:1f7e92ca7e3a3785680473329ed1091 | |||||||
| 
 | 
 | ||||||
| FROM prod-cpu AS prod-armnn | FROM prod-cpu AS prod-armnn | ||||||
| 
 | 
 | ||||||
| ENV LD_LIBRARY_PATH=/opt/armnn | ENV LD_LIBRARY_PATH=/opt/armnn \ | ||||||
|  |     LD_PRELOAD=/usr/lib/libmimalloc.so.2 | ||||||
| 
 | 
 | ||||||
| RUN apt-get update && apt-get install -y --no-install-recommends ocl-icd-libopencl1 mesa-opencl-icd libgomp1 && \ | RUN apt-get update && apt-get install -y --no-install-recommends ocl-icd-libopencl1 mesa-opencl-icd libgomp1 && \ | ||||||
|     rm -rf /var/lib/apt/lists/* && \ |     rm -rf /var/lib/apt/lists/* && \ | ||||||
| @ -114,6 +116,8 @@ COPY --from=builder-armnn \ | |||||||
| 
 | 
 | ||||||
| FROM prod-cpu AS prod-rknn | FROM prod-cpu AS prod-rknn | ||||||
| 
 | 
 | ||||||
|  | ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 | ||||||
|  | 
 | ||||||
| ADD --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/v2.3.0/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so /usr/lib/ | ADD --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/v2.3.0/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so /usr/lib/ | ||||||
| 
 | 
 | ||||||
| FROM prod-${DEVICE} AS prod | FROM prod-${DEVICE} AS prod | ||||||
| @ -126,14 +130,18 @@ RUN apt-get update && \ | |||||||
|     apt-get clean && \ |     apt-get clean && \ | ||||||
|     rm -rf /var/lib/apt/lists/* |     rm -rf /var/lib/apt/lists/* | ||||||
| 
 | 
 | ||||||
| WORKDIR /usr/src/app | RUN ln -s "/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2" /usr/lib/libmimalloc.so.2 | ||||||
|  | 
 | ||||||
|  | WORKDIR /usr/src | ||||||
| ENV TRANSFORMERS_CACHE=/cache \ | ENV TRANSFORMERS_CACHE=/cache \ | ||||||
|     PYTHONDONTWRITEBYTECODE=1 \ |     PYTHONDONTWRITEBYTECODE=1 \ | ||||||
|     PYTHONUNBUFFERED=1 \ |     PYTHONUNBUFFERED=1 \ | ||||||
|     PATH="/opt/venv/bin:$PATH" \ |     PATH="/opt/venv/bin:$PATH" \ | ||||||
|     PYTHONPATH=/usr/src \ |     PYTHONPATH=/usr/src \ | ||||||
|     DEVICE=${DEVICE} \ |     DEVICE=${DEVICE} \ | ||||||
|     VIRTUAL_ENV=/opt/venv |     VIRTUAL_ENV=/opt/venv \ | ||||||
|  |     LD_BIND_NOW=1 \ | ||||||
|  |     MACHINE_LEARNING_CACHE_FOLDER=/cache | ||||||
| 
 | 
 | ||||||
| # prevent core dumps | # prevent core dumps | ||||||
| RUN echo "hard core 0" >> /etc/security/limits.conf && \ | RUN echo "hard core 0" >> /etc/security/limits.conf && \ | ||||||
| @ -141,9 +149,7 @@ RUN echo "hard core 0" >> /etc/security/limits.conf && \ | |||||||
|     echo 'ulimit -S -c 0 > /dev/null 2>&1' >> /etc/profile |     echo 'ulimit -S -c 0 > /dev/null 2>&1' >> /etc/profile | ||||||
| 
 | 
 | ||||||
| COPY --from=builder /opt/venv /opt/venv | COPY --from=builder /opt/venv /opt/venv | ||||||
| COPY ann/ann.py /usr/src/ann/ann.py | COPY immich_ml immich_ml | ||||||
| COPY start.sh log_conf.json gunicorn_conf.py ./ |  | ||||||
| COPY app . |  | ||||||
| 
 | 
 | ||||||
| ARG BUILD_ID | ARG BUILD_ID | ||||||
| ARG BUILD_IMAGE | ARG BUILD_IMAGE | ||||||
| @ -161,6 +167,6 @@ ENV IMMICH_SOURCE_COMMIT=${BUILD_SOURCE_COMMIT} | |||||||
| ENV IMMICH_SOURCE_URL=https://github.com/immich-app/immich/commit/${BUILD_SOURCE_COMMIT} | ENV IMMICH_SOURCE_URL=https://github.com/immich-app/immich/commit/${BUILD_SOURCE_COMMIT} | ||||||
| 
 | 
 | ||||||
| ENTRYPOINT ["tini", "--"] | ENTRYPOINT ["tini", "--"] | ||||||
| CMD ["./start.sh"] | CMD ["python", "-m", "immich_ml"] | ||||||
| 
 | 
 | ||||||
| HEALTHCHECK CMD python3 healthcheck.py | HEALTHCHECK CMD python3 healthcheck.py | ||||||
| @ -8,9 +8,8 @@ from fastapi.testclient import TestClient | |||||||
| from numpy.typing import NDArray | from numpy.typing import NDArray | ||||||
| from PIL import Image | from PIL import Image | ||||||
| 
 | 
 | ||||||
| from app.config import log | from immich_ml.config import log | ||||||
| 
 | from immich_ml.main import app | ||||||
| from .main import app |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| @ -25,7 +24,7 @@ def cv_image(pil_image: Image.Image) -> NDArray[np.float32]: | |||||||
| 
 | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| def mock_get_model() -> Iterator[mock.Mock]: | def mock_get_model() -> Iterator[mock.Mock]: | ||||||
|     with mock.patch("app.models.cache.from_model_type", autospec=True) as mocked: |     with mock.patch("immich_ml.models.cache.from_model_type", autospec=True) as mocked: | ||||||
|         yield mocked |         yield mocked | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -104,14 +103,14 @@ def providers(request: pytest.FixtureRequest) -> Iterator[mock.Mock]: | |||||||
|         raise ValueError("Missing marker 'providers'") |         raise ValueError("Missing marker 'providers'") | ||||||
| 
 | 
 | ||||||
|     providers = marker.args[0] |     providers = marker.args[0] | ||||||
|     with mock.patch("app.sessions.ort.ort.get_available_providers") as mocked: |     with mock.patch("immich_ml.sessions.ort.ort.get_available_providers") as mocked: | ||||||
|         mocked.return_value = providers |         mocked.return_value = providers | ||||||
|         yield providers |         yield providers | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.fixture(scope="function") | @pytest.fixture(scope="function") | ||||||
| def ort_pybind() -> Iterator[mock.Mock]: | def ort_pybind() -> Iterator[mock.Mock]: | ||||||
|     with mock.patch("app.sessions.ort.ort.capi._pybind_state") as mocked: |     with mock.patch("immich_ml.sessions.ort.ort.capi._pybind_state") as mocked: | ||||||
|         yield mocked |         yield mocked | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -126,25 +125,25 @@ def ov_device_ids(request: pytest.FixtureRequest, ort_pybind: mock.Mock) -> Iter | |||||||
| 
 | 
 | ||||||
| @pytest.fixture(scope="function") | @pytest.fixture(scope="function") | ||||||
| def ort_session() -> Iterator[mock.Mock]: | def ort_session() -> Iterator[mock.Mock]: | ||||||
|     with mock.patch("app.sessions.ort.ort.InferenceSession") as mocked: |     with mock.patch("immich_ml.sessions.ort.ort.InferenceSession") as mocked: | ||||||
|         yield mocked |         yield mocked | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.fixture(scope="function") | @pytest.fixture(scope="function") | ||||||
| def ann_session() -> Iterator[mock.Mock]: | def ann_session() -> Iterator[mock.Mock]: | ||||||
|     with mock.patch("app.sessions.ann.Ann") as mocked: |     with mock.patch("immich_ml.sessions.ann.Ann") as mocked: | ||||||
|         yield mocked |         yield mocked | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.fixture(scope="function") | @pytest.fixture(scope="function") | ||||||
| def rknn_session() -> Iterator[mock.Mock]: | def rknn_session() -> Iterator[mock.Mock]: | ||||||
|     with mock.patch("app.sessions.rknn.RknnPoolExecutor") as mocked: |     with mock.patch("immich_ml.sessions.rknn.RknnPoolExecutor") as mocked: | ||||||
|         yield mocked |         yield mocked | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.fixture(scope="function") | @pytest.fixture(scope="function") | ||||||
| def rmtree() -> Iterator[mock.Mock]: | def rmtree() -> Iterator[mock.Mock]: | ||||||
|     with mock.patch("app.models.base.rmtree", autospec=True) as mocked: |     with mock.patch("immich_ml.models.base.rmtree", autospec=True) as mocked: | ||||||
|         mocked.avoids_symlink_attacks = True |         mocked.avoids_symlink_attacks = True | ||||||
|         yield mocked |         yield mocked | ||||||
| 
 | 
 | ||||||
| @ -158,7 +157,7 @@ def path() -> Iterator[mock.Mock]: | |||||||
|     path.with_suffix.return_value = path |     path.with_suffix.return_value = path | ||||||
|     path.return_value = path |     path.return_value = path | ||||||
| 
 | 
 | ||||||
|     with mock.patch("app.models.base.Path", return_value=path) as mocked: |     with mock.patch("immich_ml.models.base.Path", return_value=path) as mocked: | ||||||
|         yield mocked |         yield mocked | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -182,5 +181,5 @@ def exception() -> Iterator[mock.Mock]: | |||||||
| 
 | 
 | ||||||
| @pytest.fixture(scope="function") | @pytest.fixture(scope="function") | ||||||
| def snapshot_download() -> Iterator[mock.Mock]: | def snapshot_download() -> Iterator[mock.Mock]: | ||||||
|     with mock.patch("app.models.base.snapshot_download") as mocked: |     with mock.patch("immich_ml.models.base.snapshot_download") as mocked: | ||||||
|         yield mocked |         yield mocked | ||||||
							
								
								
									
										43
									
								
								machine-learning/immich_ml/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								machine-learning/immich_ml/__main__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | import os | ||||||
|  | import signal | ||||||
|  | import subprocess | ||||||
|  | from pathlib import Path | ||||||
|  | 
 | ||||||
|  | from .config import log, non_prefixed_settings, settings | ||||||
|  | 
 | ||||||
|  | if source_ref := os.getenv("IMMICH_SOURCE_REF"): | ||||||
|  |     log.info(f"Initializing Immich ML [{source_ref}]") | ||||||
|  | else: | ||||||
|  |     log.info("Initializing Immich ML") | ||||||
|  | 
 | ||||||
|  | module_dir = Path(__file__).parent | ||||||
|  | 
 | ||||||
|  | try: | ||||||
|  |     with subprocess.Popen( | ||||||
|  |         [ | ||||||
|  |             "python", | ||||||
|  |             "-m", | ||||||
|  |             "gunicorn", | ||||||
|  |             "immich_ml.main:app", | ||||||
|  |             "-k", | ||||||
|  |             "immich_ml.config.CustomUvicornWorker", | ||||||
|  |             "-c", | ||||||
|  |             module_dir / "gunicorn_conf.py", | ||||||
|  |             "-b", | ||||||
|  |             f"{non_prefixed_settings.immich_host}:{non_prefixed_settings.immich_port}", | ||||||
|  |             "-w", | ||||||
|  |             str(settings.workers), | ||||||
|  |             "-t", | ||||||
|  |             str(settings.worker_timeout), | ||||||
|  |             "--log-config-json", | ||||||
|  |             module_dir / "log_conf.json", | ||||||
|  |             "--keep-alive", | ||||||
|  |             str(settings.http_keepalive_timeout_s), | ||||||
|  |             "--graceful-timeout", | ||||||
|  |             "10", | ||||||
|  |         ], | ||||||
|  |     ) as cmd: | ||||||
|  |         cmd.wait() | ||||||
|  | except KeyboardInterrupt: | ||||||
|  |     cmd.send_signal(signal.SIGINT) | ||||||
|  | exit(cmd.returncode) | ||||||
| @ -51,12 +51,12 @@ class Settings(BaseSettings): | |||||||
|         protected_namespaces=("settings_",), |         protected_namespaces=("settings_",), | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     cache_folder: Path = Path("/cache") |     cache_folder: Path = (Path.home() / ".cache" / "immich_ml").resolve() | ||||||
|     model_ttl: int = 300 |     model_ttl: int = 300 | ||||||
|     model_ttl_poll_s: int = 10 |     model_ttl_poll_s: int = 10 | ||||||
|     host: str = "0.0.0.0" |  | ||||||
|     port: int = 3003 |  | ||||||
|     workers: int = 1 |     workers: int = 1 | ||||||
|  |     worker_timeout: int = 300 | ||||||
|  |     http_keepalive_timeout_s: int = 2 | ||||||
|     test_full: bool = False |     test_full: bool = False | ||||||
|     request_threads: int = os.cpu_count() or 4 |     request_threads: int = os.cpu_count() or 4 | ||||||
|     model_inter_op_threads: int = 0 |     model_inter_op_threads: int = 0 | ||||||
| @ -74,9 +74,11 @@ class Settings(BaseSettings): | |||||||
|         return os.environ.get("MACHINE_LEARNING_DEVICE_ID", "0") |         return os.environ.get("MACHINE_LEARNING_DEVICE_ID", "0") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class LogSettings(BaseSettings): | class NonPrefixedSettings(BaseSettings): | ||||||
|     model_config = SettingsConfigDict(case_sensitive=False) |     model_config = SettingsConfigDict(case_sensitive=False) | ||||||
| 
 | 
 | ||||||
|  |     immich_host: str = "[::]" | ||||||
|  |     immich_port: int = 3003 | ||||||
|     immich_log_level: str = "info" |     immich_log_level: str = "info" | ||||||
|     no_color: bool = False |     no_color: bool = False | ||||||
| 
 | 
 | ||||||
| @ -100,14 +102,14 @@ LOG_LEVELS: dict[str, int] = { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| settings = Settings() | settings = Settings() | ||||||
| log_settings = LogSettings() | non_prefixed_settings = NonPrefixedSettings() | ||||||
| 
 | 
 | ||||||
| LOG_LEVEL = LOG_LEVELS.get(log_settings.immich_log_level.lower(), logging.INFO) | LOG_LEVEL = LOG_LEVELS.get(non_prefixed_settings.immich_log_level.lower(), logging.INFO) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CustomRichHandler(RichHandler): | class CustomRichHandler(RichHandler): | ||||||
|     def __init__(self) -> None: |     def __init__(self) -> None: | ||||||
|         console = Console(color_system="standard", no_color=log_settings.no_color) |         console = Console(color_system="standard", no_color=non_prefixed_settings.no_color) | ||||||
|         self.excluded = ["uvicorn", "starlette", "fastapi"] |         self.excluded = ["uvicorn", "starlette", "fastapi"] | ||||||
|         super().__init__( |         super().__init__( | ||||||
|             show_path=False, |             show_path=False, | ||||||
							
								
								
									
										21
									
								
								machine-learning/immich_ml/log_conf.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								machine-learning/immich_ml/log_conf.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | { | ||||||
|  |   "version": 1, | ||||||
|  |   "disable_existing_loggers": false, | ||||||
|  |   "handlers": { | ||||||
|  |     "console": { | ||||||
|  |       "class": "immich_ml.config.CustomRichHandler" | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "loggers": { | ||||||
|  |     "gunicorn.error": { | ||||||
|  |       "handlers": [ | ||||||
|  |         "console" | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "root": { | ||||||
|  |     "handlers": [ | ||||||
|  |       "console" | ||||||
|  |     ] | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -18,9 +18,9 @@ from PIL.Image import Image | |||||||
| from pydantic import ValidationError | from pydantic import ValidationError | ||||||
| from starlette.formparsers import MultiPartParser | from starlette.formparsers import MultiPartParser | ||||||
| 
 | 
 | ||||||
| from app.models import get_model_deps | from immich_ml.models import get_model_deps | ||||||
| from app.models.base import InferenceModel | from immich_ml.models.base import InferenceModel | ||||||
| from app.models.transforms import decode_pil | from immich_ml.models.transforms import decode_pil | ||||||
| 
 | 
 | ||||||
| from .config import PreloadModelData, log, settings | from .config import PreloadModelData, log, settings | ||||||
| from .models.cache import ModelCache | from .models.cache import ModelCache | ||||||
| @ -1,9 +1,9 @@ | |||||||
| from typing import Any | from typing import Any | ||||||
| 
 | 
 | ||||||
| from app.models.base import InferenceModel | from immich_ml.models.base import InferenceModel | ||||||
| from app.models.clip.textual import MClipTextualEncoder, OpenClipTextualEncoder | from immich_ml.models.clip.textual import MClipTextualEncoder, OpenClipTextualEncoder | ||||||
| from app.models.clip.visual import OpenClipVisualEncoder | from immich_ml.models.clip.visual import OpenClipVisualEncoder | ||||||
| from app.schemas import ModelSource, ModelTask, ModelType | from immich_ml.schemas import ModelSource, ModelTask, ModelType | ||||||
| 
 | 
 | ||||||
| from .constants import get_model_source | from .constants import get_model_source | ||||||
| from .facial_recognition.detection import FaceDetector | from .facial_recognition.detection import FaceDetector | ||||||
| @ -7,9 +7,9 @@ from typing import Any, ClassVar | |||||||
| 
 | 
 | ||||||
| from huggingface_hub import snapshot_download | from huggingface_hub import snapshot_download | ||||||
| 
 | 
 | ||||||
| import ann.ann | import immich_ml.sessions.ann.loader | ||||||
| import app.sessions.rknn as rknn | import immich_ml.sessions.rknn as rknn | ||||||
| from app.sessions.ort import OrtSession | from immich_ml.sessions.ort import OrtSession | ||||||
| 
 | 
 | ||||||
| from ..config import clean_name, log, settings | from ..config import clean_name, log, settings | ||||||
| from ..schemas import ModelFormat, ModelIdentity, ModelSession, ModelTask, ModelType | from ..schemas import ModelFormat, ModelIdentity, ModelSession, ModelTask, ModelType | ||||||
| @ -171,7 +171,7 @@ class InferenceModel(ABC): | |||||||
|     def _model_format_default(self) -> ModelFormat: |     def _model_format_default(self) -> ModelFormat: | ||||||
|         if rknn.is_available: |         if rknn.is_available: | ||||||
|             return ModelFormat.RKNN |             return ModelFormat.RKNN | ||||||
|         elif ann.ann.is_available and settings.ann: |         elif immich_ml.sessions.ann.loader.is_available and settings.ann: | ||||||
|             return ModelFormat.ARMNN |             return ModelFormat.ARMNN | ||||||
|         else: |         else: | ||||||
|             return ModelFormat.ONNX |             return ModelFormat.ONNX | ||||||
| @ -4,8 +4,8 @@ from aiocache.backends.memory import SimpleMemoryCache | |||||||
| from aiocache.lock import OptimisticLock | from aiocache.lock import OptimisticLock | ||||||
| from aiocache.plugins import TimingPlugin | from aiocache.plugins import TimingPlugin | ||||||
| 
 | 
 | ||||||
| from app.models import from_model_type | from immich_ml.models import from_model_type | ||||||
| from app.models.base import InferenceModel | from immich_ml.models.base import InferenceModel | ||||||
| 
 | 
 | ||||||
| from ..schemas import ModelTask, ModelType, has_profiling | from ..schemas import ModelTask, ModelType, has_profiling | ||||||
| 
 | 
 | ||||||
| @ -8,10 +8,10 @@ import numpy as np | |||||||
| from numpy.typing import NDArray | from numpy.typing import NDArray | ||||||
| from tokenizers import Encoding, Tokenizer | from tokenizers import Encoding, Tokenizer | ||||||
| 
 | 
 | ||||||
| from app.config import log | from immich_ml.config import log | ||||||
| from app.models.base import InferenceModel | from immich_ml.models.base import InferenceModel | ||||||
| from app.models.transforms import clean_text, serialize_np_array | from immich_ml.models.transforms import clean_text, serialize_np_array | ||||||
| from app.schemas import ModelSession, ModelTask, ModelType | from immich_ml.schemas import ModelSession, ModelTask, ModelType | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class BaseCLIPTextualEncoder(InferenceModel): | class BaseCLIPTextualEncoder(InferenceModel): | ||||||
| @ -8,9 +8,9 @@ import numpy as np | |||||||
| from numpy.typing import NDArray | from numpy.typing import NDArray | ||||||
| from PIL import Image | from PIL import Image | ||||||
| 
 | 
 | ||||||
| from app.config import log | from immich_ml.config import log | ||||||
| from app.models.base import InferenceModel | from immich_ml.models.base import InferenceModel | ||||||
| from app.models.transforms import ( | from immich_ml.models.transforms import ( | ||||||
|     crop_pil, |     crop_pil, | ||||||
|     decode_pil, |     decode_pil, | ||||||
|     get_pil_resampling, |     get_pil_resampling, | ||||||
| @ -19,7 +19,7 @@ from app.models.transforms import ( | |||||||
|     serialize_np_array, |     serialize_np_array, | ||||||
|     to_numpy, |     to_numpy, | ||||||
| ) | ) | ||||||
| from app.schemas import ModelSession, ModelTask, ModelType | from immich_ml.schemas import ModelSession, ModelTask, ModelType | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class BaseCLIPVisualEncoder(InferenceModel): | class BaseCLIPVisualEncoder(InferenceModel): | ||||||
| @ -1,5 +1,5 @@ | |||||||
| from app.config import clean_name | from immich_ml.config import clean_name | ||||||
| from app.schemas import ModelSource | from immich_ml.schemas import ModelSource | ||||||
| 
 | 
 | ||||||
| _OPENCLIP_MODELS = { | _OPENCLIP_MODELS = { | ||||||
|     "RN101__openai", |     "RN101__openai", | ||||||
| @ -4,9 +4,9 @@ import numpy as np | |||||||
| from insightface.model_zoo import RetinaFace | from insightface.model_zoo import RetinaFace | ||||||
| from numpy.typing import NDArray | from numpy.typing import NDArray | ||||||
| 
 | 
 | ||||||
| from app.models.base import InferenceModel | from immich_ml.models.base import InferenceModel | ||||||
| from app.models.transforms import decode_cv2 | from immich_ml.models.transforms import decode_cv2 | ||||||
| from app.schemas import FaceDetectionOutput, ModelSession, ModelTask, ModelType | from immich_ml.schemas import FaceDetectionOutput, ModelSession, ModelTask, ModelType | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class FaceDetector(InferenceModel): | class FaceDetector(InferenceModel): | ||||||
| @ -10,10 +10,17 @@ from numpy.typing import NDArray | |||||||
| from onnx.tools.update_model_dims import update_inputs_outputs_dims | from onnx.tools.update_model_dims import update_inputs_outputs_dims | ||||||
| from PIL import Image | from PIL import Image | ||||||
| 
 | 
 | ||||||
| from app.config import log, settings | from immich_ml.config import log, settings | ||||||
| from app.models.base import InferenceModel | from immich_ml.models.base import InferenceModel | ||||||
| from app.models.transforms import decode_cv2, serialize_np_array | from immich_ml.models.transforms import decode_cv2, serialize_np_array | ||||||
| from app.schemas import FaceDetectionOutput, FacialRecognitionOutput, ModelFormat, ModelSession, ModelTask, ModelType | from immich_ml.schemas import ( | ||||||
|  |     FaceDetectionOutput, | ||||||
|  |     FacialRecognitionOutput, | ||||||
|  |     ModelFormat, | ||||||
|  |     ModelSession, | ||||||
|  |     ModelTask, | ||||||
|  |     ModelType, | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class FaceRecognizer(InferenceModel): | class FaceRecognizer(InferenceModel): | ||||||
| @ -6,10 +6,10 @@ from typing import Any, NamedTuple | |||||||
| import numpy as np | import numpy as np | ||||||
| from numpy.typing import NDArray | from numpy.typing import NDArray | ||||||
| 
 | 
 | ||||||
| from ann.ann import Ann | from immich_ml.config import log, settings | ||||||
| from app.schemas import SessionNode | from immich_ml.schemas import SessionNode | ||||||
| 
 | 
 | ||||||
| from ..config import log, settings | from .loader import Ann | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AnnSession: | class AnnSession: | ||||||
| @ -7,7 +7,7 @@ from typing import Any, Protocol, TypeVar | |||||||
| import numpy as np | import numpy as np | ||||||
| from numpy.typing import NDArray | from numpy.typing import NDArray | ||||||
| 
 | 
 | ||||||
| from app.config import log | from immich_ml.config import log | ||||||
| 
 | 
 | ||||||
| try: | try: | ||||||
|     CDLL("libmali.so")  # fail if libmali.so is not mounted into container |     CDLL("libmali.so")  # fail if libmali.so is not mounted into container | ||||||
| @ -7,8 +7,8 @@ import numpy as np | |||||||
| import onnxruntime as ort | import onnxruntime as ort | ||||||
| from numpy.typing import NDArray | from numpy.typing import NDArray | ||||||
| 
 | 
 | ||||||
| from app.models.constants import SUPPORTED_PROVIDERS | from immich_ml.models.constants import SUPPORTED_PROVIDERS | ||||||
| from app.schemas import SessionNode | from immich_ml.schemas import SessionNode | ||||||
| 
 | 
 | ||||||
| from ..config import log, settings | from ..config import log, settings | ||||||
| 
 | 
 | ||||||
| @ -6,8 +6,8 @@ from typing import Any, NamedTuple | |||||||
| import numpy as np | import numpy as np | ||||||
| from numpy.typing import NDArray | from numpy.typing import NDArray | ||||||
| 
 | 
 | ||||||
| from app.config import log, settings | from immich_ml.config import log, settings | ||||||
| from app.schemas import SessionNode | from immich_ml.schemas import SessionNode | ||||||
| 
 | 
 | ||||||
| from .rknnpool import RknnPoolExecutor, is_available, soc_name | from .rknnpool import RknnPoolExecutor, is_available, soc_name | ||||||
| 
 | 
 | ||||||
| @ -10,8 +10,8 @@ from typing import Callable | |||||||
| import numpy as np | import numpy as np | ||||||
| from numpy.typing import NDArray | from numpy.typing import NDArray | ||||||
| 
 | 
 | ||||||
| from app.config import log | from immich_ml.config import log | ||||||
| from app.models.constants import RKNN_COREMASK_SUPPORTED_SOCS, RKNN_SUPPORTED_SOCS | from immich_ml.models.constants import RKNN_COREMASK_SUPPORTED_SOCS, RKNN_SUPPORTED_SOCS | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def get_soc(device_tree_path: Path | str) -> str | None: | def get_soc(device_tree_path: Path | str) -> str | None: | ||||||
| @ -1,15 +0,0 @@ | |||||||
| { |  | ||||||
|   "version": 1, |  | ||||||
|   "disable_existing_loggers": false, |  | ||||||
|   "handlers": { |  | ||||||
|     "console": { |  | ||||||
|       "class": "app.config.CustomRichHandler" |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
|   "loggers": { |  | ||||||
|     "gunicorn.error": { |  | ||||||
|       "handlers": ["console"] |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
|   "root": { "handlers": ["console"] } |  | ||||||
| } |  | ||||||
| @ -1,5 +1,5 @@ | |||||||
| [project] | [project] | ||||||
| name = "machine-learning" | name = "immich-ml" | ||||||
| version = "1.129.0" | version = "1.129.0" | ||||||
| description = "" | description = "" | ||||||
| authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }] | authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }] | ||||||
| @ -66,10 +66,10 @@ explicit = true | |||||||
| onnxruntime-gpu = { index = "cuda12" } | onnxruntime-gpu = { index = "cuda12" } | ||||||
| 
 | 
 | ||||||
| [tool.hatch.build.targets.sdist] | [tool.hatch.build.targets.sdist] | ||||||
| include = ["app"] | include = ["immich_ml"] | ||||||
| 
 | 
 | ||||||
| [tool.hatch.build.targets.wheel] | [tool.hatch.build.targets.wheel] | ||||||
| include = ["app"] | include = ["immich_ml"] | ||||||
| 
 | 
 | ||||||
| [build-system] | [build-system] | ||||||
| requires = ["hatchling"] | requires = ["hatchling"] | ||||||
|  | |||||||
| @ -1,31 +0,0 @@ | |||||||
| #!/usr/bin/env sh |  | ||||||
| 
 |  | ||||||
| echo "Initializing Immich ML $IMMICH_SOURCE_REF" |  | ||||||
| 
 |  | ||||||
| if ! [ "$DEVICE" = "openvino" ]; then |  | ||||||
| 	: "${MACHINE_LEARNING_WORKER_TIMEOUT:=120}" |  | ||||||
| else |  | ||||||
| 	: "${MACHINE_LEARNING_WORKER_TIMEOUT:=300}" |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| # mimalloc seems to increase memory usage dramatically with openvino, need to investigate |  | ||||||
| if ! [ "$DEVICE" = "openvino" ] && ! [ "$DEVICE" = "rocm" ]; then |  | ||||||
| 	lib_path="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2" |  | ||||||
| 	export LD_PRELOAD="$lib_path" |  | ||||||
| 	export LD_BIND_NOW=1 |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| : "${IMMICH_HOST:=[::]}" |  | ||||||
| : "${IMMICH_PORT:=3003}" |  | ||||||
| : "${MACHINE_LEARNING_WORKERS:=1}" |  | ||||||
| : "${MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S:=2}" |  | ||||||
| 
 |  | ||||||
| gunicorn app.main:app \ |  | ||||||
| 	-k app.config.CustomUvicornWorker \ |  | ||||||
| 	-c gunicorn_conf.py \ |  | ||||||
| 	-b "$IMMICH_HOST":"$IMMICH_PORT" \ |  | ||||||
| 	-w "$MACHINE_LEARNING_WORKERS" \ |  | ||||||
| 	-t "$MACHINE_LEARNING_WORKER_TIMEOUT" \ |  | ||||||
| 	--log-config-json log_conf.json \ |  | ||||||
| 	--keep-alive "$MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S" \ |  | ||||||
| 	--graceful-timeout 0 |  | ||||||
| @ -18,19 +18,18 @@ from PIL import Image | |||||||
| from pytest import MonkeyPatch | from pytest import MonkeyPatch | ||||||
| from pytest_mock import MockerFixture | from pytest_mock import MockerFixture | ||||||
| 
 | 
 | ||||||
| from app.main import load, preload_models | from immich_ml.config import Settings, settings | ||||||
| from app.models.clip.textual import MClipTextualEncoder, OpenClipTextualEncoder | from immich_ml.main import load, preload_models | ||||||
| from app.models.clip.visual import OpenClipVisualEncoder | from immich_ml.models.base import InferenceModel | ||||||
| from app.models.facial_recognition.detection import FaceDetector | from immich_ml.models.cache import ModelCache | ||||||
| from app.models.facial_recognition.recognition import FaceRecognizer | from immich_ml.models.clip.textual import MClipTextualEncoder, OpenClipTextualEncoder | ||||||
| from app.sessions.ann import AnnSession | from immich_ml.models.clip.visual import OpenClipVisualEncoder | ||||||
| from app.sessions.ort import OrtSession | from immich_ml.models.facial_recognition.detection import FaceDetector | ||||||
| from app.sessions.rknn import RknnSession, run_inference | from immich_ml.models.facial_recognition.recognition import FaceRecognizer | ||||||
| 
 | from immich_ml.schemas import ModelFormat, ModelTask, ModelType | ||||||
| from .config import Settings, settings | from immich_ml.sessions.ann import AnnSession | ||||||
| from .models.base import InferenceModel | from immich_ml.sessions.ort import OrtSession | ||||||
| from .models.cache import ModelCache | from immich_ml.sessions.rknn import RknnSession, run_inference | ||||||
| from .schemas import ModelFormat, ModelTask, ModelType |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestBase: | class TestBase: | ||||||
| @ -47,7 +46,7 @@ class TestBase: | |||||||
| 
 | 
 | ||||||
|     def test_sets_default_model_format(self, mocker: MockerFixture) -> None: |     def test_sets_default_model_format(self, mocker: MockerFixture) -> None: | ||||||
|         mocker.patch.object(settings, "ann", True) |         mocker.patch.object(settings, "ann", True) | ||||||
|         mocker.patch("ann.ann.is_available", False) |         mocker.patch("immich_ml.sessions.ann.loader.is_available", False) | ||||||
| 
 | 
 | ||||||
|         encoder = OpenClipTextualEncoder("ViT-B-32__openai") |         encoder = OpenClipTextualEncoder("ViT-B-32__openai") | ||||||
| 
 | 
 | ||||||
| @ -55,7 +54,7 @@ class TestBase: | |||||||
| 
 | 
 | ||||||
|     def test_sets_default_model_format_to_armnn_if_available(self, path: mock.Mock, mocker: MockerFixture) -> None: |     def test_sets_default_model_format_to_armnn_if_available(self, path: mock.Mock, mocker: MockerFixture) -> None: | ||||||
|         mocker.patch.object(settings, "ann", True) |         mocker.patch.object(settings, "ann", True) | ||||||
|         mocker.patch("ann.ann.is_available", True) |         mocker.patch("immich_ml.sessions.ann.loader.is_available", True) | ||||||
|         path.suffix = ".armnn" |         path.suffix = ".armnn" | ||||||
| 
 | 
 | ||||||
|         encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=path) |         encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=path) | ||||||
| @ -64,7 +63,7 @@ class TestBase: | |||||||
| 
 | 
 | ||||||
|     def test_sets_model_format_kwarg(self, mocker: MockerFixture) -> None: |     def test_sets_model_format_kwarg(self, mocker: MockerFixture) -> None: | ||||||
|         mocker.patch.object(settings, "ann", False) |         mocker.patch.object(settings, "ann", False) | ||||||
|         mocker.patch("ann.ann.is_available", False) |         mocker.patch("immich_ml.sessions.ann.loader.is_available", False) | ||||||
| 
 | 
 | ||||||
|         encoder = OpenClipTextualEncoder("ViT-B-32__openai", model_format=ModelFormat.ARMNN) |         encoder = OpenClipTextualEncoder("ViT-B-32__openai", model_format=ModelFormat.ARMNN) | ||||||
| 
 | 
 | ||||||
| @ -72,7 +71,7 @@ class TestBase: | |||||||
| 
 | 
 | ||||||
|     def test_sets_default_model_format_to_rknn_if_available(self, mocker: MockerFixture) -> None: |     def test_sets_default_model_format_to_rknn_if_available(self, mocker: MockerFixture) -> None: | ||||||
|         mocker.patch.object(settings, "rknn", True) |         mocker.patch.object(settings, "rknn", True) | ||||||
|         mocker.patch("app.sessions.rknn.is_available", True) |         mocker.patch("immich_ml.sessions.rknn.is_available", True) | ||||||
| 
 | 
 | ||||||
|         encoder = OpenClipTextualEncoder("ViT-B-32__openai") |         encoder = OpenClipTextualEncoder("ViT-B-32__openai") | ||||||
| 
 | 
 | ||||||
| @ -294,7 +293,7 @@ class TestOrtSession: | |||||||
|         assert session.sess_options.intra_op_num_threads == 0 |         assert session.sess_options.intra_op_num_threads == 0 | ||||||
| 
 | 
 | ||||||
|     def test_sets_default_sess_options_sets_threads_if_non_cpu_and_set_threads(self, mocker: MockerFixture) -> None: |     def test_sets_default_sess_options_sets_threads_if_non_cpu_and_set_threads(self, mocker: MockerFixture) -> None: | ||||||
|         mock_settings = mocker.patch("app.sessions.ort.settings", autospec=True) |         mock_settings = mocker.patch("immich_ml.sessions.ort.settings", autospec=True) | ||||||
|         mock_settings.model_inter_op_threads = 2 |         mock_settings.model_inter_op_threads = 2 | ||||||
|         mock_settings.model_intra_op_threads = 4 |         mock_settings.model_intra_op_threads = 4 | ||||||
| 
 | 
 | ||||||
| @ -373,8 +372,8 @@ class TestRknnSession: | |||||||
|     def test_creates_rknn_session(self, rknn_session: mock.Mock, info: mock.Mock, mocker: MockerFixture) -> None: |     def test_creates_rknn_session(self, rknn_session: mock.Mock, info: mock.Mock, mocker: MockerFixture) -> None: | ||||||
|         model_path = mock.MagicMock(spec=Path) |         model_path = mock.MagicMock(spec=Path) | ||||||
|         tpe = 1 |         tpe = 1 | ||||||
|         mocker.patch("app.sessions.rknn.soc_name", "rk3566") |         mocker.patch("immich_ml.sessions.rknn.soc_name", "rk3566") | ||||||
|         mocker.patch("app.sessions.rknn.is_available", True) |         mocker.patch("immich_ml.sessions.rknn.is_available", True) | ||||||
|         RknnSession(model_path) |         RknnSession(model_path) | ||||||
| 
 | 
 | ||||||
|         rknn_session.assert_called_once_with(model_path=model_path.as_posix(), tpes=tpe, func=run_inference) |         rknn_session.assert_called_once_with(model_path=model_path.as_posix(), tpes=tpe, func=run_inference) | ||||||
| @ -384,7 +383,7 @@ class TestRknnSession: | |||||||
|     def test_run_rknn(self, rknn_session: mock.Mock, mocker: MockerFixture) -> None: |     def test_run_rknn(self, rknn_session: mock.Mock, mocker: MockerFixture) -> None: | ||||||
|         rknn_session.return_value.load.return_value = 123 |         rknn_session.return_value.load.return_value = 123 | ||||||
|         np_spy = mocker.spy(np, "ascontiguousarray") |         np_spy = mocker.spy(np, "ascontiguousarray") | ||||||
|         mocker.patch("app.sessions.rknn.soc_name", "rk3566") |         mocker.patch("immich_ml.sessions.rknn.soc_name", "rk3566") | ||||||
|         session = RknnSession(Path("ViT-B-32__openai")) |         session = RknnSession(Path("ViT-B-32__openai")) | ||||||
|         [input1, input2] = [np.random.rand(1, 3, 224, 224).astype(np.float32) for _ in range(2)] |         [input1, input2] = [np.random.rand(1, 3, 224, 224).astype(np.float32) for _ in range(2)] | ||||||
|         input_feed = {"input.1": input1, "input.2": input2} |         input_feed = {"input.1": input1, "input.2": input2} | ||||||
| @ -434,7 +433,7 @@ class TestCLIP: | |||||||
| 
 | 
 | ||||||
|         mocked = mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value |         mocked = mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value | ||||||
|         mocked.run.return_value = [[self.embedding]] |         mocked.run.return_value = [[self.embedding]] | ||||||
|         mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True) |         mocker.patch("immich_ml.models.clip.textual.Tokenizer.from_file", autospec=True) | ||||||
| 
 | 
 | ||||||
|         clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache") |         clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache") | ||||||
|         embedding_str = clip_encoder.predict("test search query") |         embedding_str = clip_encoder.predict("test search query") | ||||||
| @ -454,7 +453,7 @@ class TestCLIP: | |||||||
|         mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg) |         mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg) | ||||||
|         mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) |         mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) | ||||||
|         mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value |         mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value | ||||||
|         mock_tokenizer = mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True).return_value |         mock_tokenizer = mocker.patch("immich_ml.models.clip.textual.Tokenizer.from_file", autospec=True).return_value | ||||||
|         mock_ids = [randint(0, 50000) for _ in range(77)] |         mock_ids = [randint(0, 50000) for _ in range(77)] | ||||||
|         mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids) |         mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids) | ||||||
| 
 | 
 | ||||||
| @ -480,7 +479,7 @@ class TestCLIP: | |||||||
|         mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg) |         mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg) | ||||||
|         mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) |         mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) | ||||||
|         mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value |         mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value | ||||||
|         mock_tokenizer = mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True).return_value |         mock_tokenizer = mocker.patch("immich_ml.models.clip.textual.Tokenizer.from_file", autospec=True).return_value | ||||||
|         mock_ids = [randint(0, 50000) for _ in range(77)] |         mock_ids = [randint(0, 50000) for _ in range(77)] | ||||||
|         mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids) |         mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids) | ||||||
| 
 | 
 | ||||||
| @ -505,7 +504,7 @@ class TestCLIP: | |||||||
|         mocker.patch.object(MClipTextualEncoder, "model_cfg", clip_model_cfg) |         mocker.patch.object(MClipTextualEncoder, "model_cfg", clip_model_cfg) | ||||||
|         mocker.patch.object(MClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) |         mocker.patch.object(MClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) | ||||||
|         mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value |         mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value | ||||||
|         mock_tokenizer = mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True).return_value |         mock_tokenizer = mocker.patch("immich_ml.models.clip.textual.Tokenizer.from_file", autospec=True).return_value | ||||||
|         mock_ids = [randint(0, 50000) for _ in range(77)] |         mock_ids = [randint(0, 50000) for _ in range(77)] | ||||||
|         mock_attention_mask = [randint(0, 1) for _ in range(77)] |         mock_attention_mask = [randint(0, 1) for _ in range(77)] | ||||||
|         mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids, attention_mask=mock_attention_mask) |         mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids, attention_mask=mock_attention_mask) | ||||||
| @ -597,12 +596,12 @@ class TestFaceRecognition: | |||||||
|     def test_recognition_adds_batch_axis_for_ort( |     def test_recognition_adds_batch_axis_for_ort( | ||||||
|         self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture |         self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         onnx = mocker.patch("app.models.facial_recognition.recognition.onnx", autospec=True) |         onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) | ||||||
|         update_dims = mocker.patch( |         update_dims = mocker.patch( | ||||||
|             "app.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True |             "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True | ||||||
|         ) |         ) | ||||||
|         mocker.patch("app.models.base.InferenceModel.download") |         mocker.patch("immich_ml.models.base.InferenceModel.download") | ||||||
|         mocker.patch("app.models.facial_recognition.recognition.ArcFaceONNX") |         mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") | ||||||
|         ort_session.return_value.get_inputs.return_value = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))] |         ort_session.return_value.get_inputs.return_value = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))] | ||||||
|         ort_session.return_value.get_outputs.return_value = [SimpleNamespace(name="output.1", shape=(1, 800))] |         ort_session.return_value.get_outputs.return_value = [SimpleNamespace(name="output.1", shape=(1, 800))] | ||||||
|         path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" |         path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" | ||||||
| @ -631,12 +630,12 @@ class TestFaceRecognition: | |||||||
|     def test_recognition_does_not_add_batch_axis_if_exists( |     def test_recognition_does_not_add_batch_axis_if_exists( | ||||||
|         self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture |         self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         onnx = mocker.patch("app.models.facial_recognition.recognition.onnx", autospec=True) |         onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) | ||||||
|         update_dims = mocker.patch( |         update_dims = mocker.patch( | ||||||
|             "app.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True |             "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True | ||||||
|         ) |         ) | ||||||
|         mocker.patch("app.models.base.InferenceModel.download") |         mocker.patch("immich_ml.models.base.InferenceModel.download") | ||||||
|         mocker.patch("app.models.facial_recognition.recognition.ArcFaceONNX") |         mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") | ||||||
|         path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" |         path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" | ||||||
| 
 | 
 | ||||||
|         inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] |         inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] | ||||||
| @ -655,12 +654,12 @@ class TestFaceRecognition: | |||||||
|     def test_recognition_does_not_add_batch_axis_for_armnn( |     def test_recognition_does_not_add_batch_axis_for_armnn( | ||||||
|         self, ann_session: mock.Mock, path: mock.Mock, mocker: MockerFixture |         self, ann_session: mock.Mock, path: mock.Mock, mocker: MockerFixture | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         onnx = mocker.patch("app.models.facial_recognition.recognition.onnx", autospec=True) |         onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) | ||||||
|         update_dims = mocker.patch( |         update_dims = mocker.patch( | ||||||
|             "app.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True |             "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True | ||||||
|         ) |         ) | ||||||
|         mocker.patch("app.models.base.InferenceModel.download") |         mocker.patch("immich_ml.models.base.InferenceModel.download") | ||||||
|         mocker.patch("app.models.facial_recognition.recognition.ArcFaceONNX") |         mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") | ||||||
|         path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".armnn" |         path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".armnn" | ||||||
| 
 | 
 | ||||||
|         inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] |         inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] | ||||||
| @ -679,12 +678,12 @@ class TestFaceRecognition: | |||||||
|     def test_recognition_does_not_add_batch_axis_for_openvino( |     def test_recognition_does_not_add_batch_axis_for_openvino( | ||||||
|         self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture |         self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         onnx = mocker.patch("app.models.facial_recognition.recognition.onnx", autospec=True) |         onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) | ||||||
|         update_dims = mocker.patch( |         update_dims = mocker.patch( | ||||||
|             "app.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True |             "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True | ||||||
|         ) |         ) | ||||||
|         mocker.patch("app.models.base.InferenceModel.download") |         mocker.patch("immich_ml.models.base.InferenceModel.download") | ||||||
|         mocker.patch("app.models.facial_recognition.recognition.ArcFaceONNX") |         mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") | ||||||
|         path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" |         path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" | ||||||
| 
 | 
 | ||||||
|         inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] |         inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] | ||||||
| @ -733,13 +732,13 @@ class TestCache: | |||||||
|         ) |         ) | ||||||
|         assert len(model_cache.cache._cache) == 2 |         assert len(model_cache.cache._cache) == 2 | ||||||
| 
 | 
 | ||||||
|     @mock.patch("app.models.cache.OptimisticLock", autospec=True) |     @mock.patch("immich_ml.models.cache.OptimisticLock", autospec=True) | ||||||
|     async def test_model_ttl(self, mock_lock_cls: mock.Mock, mock_get_model: mock.Mock) -> None: |     async def test_model_ttl(self, mock_lock_cls: mock.Mock, mock_get_model: mock.Mock) -> None: | ||||||
|         model_cache = ModelCache() |         model_cache = ModelCache() | ||||||
|         await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100) |         await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100) | ||||||
|         mock_lock_cls.return_value.__aenter__.return_value.cas.assert_called_with(mock.ANY, ttl=100) |         mock_lock_cls.return_value.__aenter__.return_value.cas.assert_called_with(mock.ANY, ttl=100) | ||||||
| 
 | 
 | ||||||
|     @mock.patch("app.models.cache.SimpleMemoryCache.expire") |     @mock.patch("immich_ml.models.cache.SimpleMemoryCache.expire") | ||||||
|     async def test_revalidate_get(self, mock_cache_expire: mock.Mock, mock_get_model: mock.Mock) -> None: |     async def test_revalidate_get(self, mock_cache_expire: mock.Mock, mock_get_model: mock.Mock) -> None: | ||||||
|         model_cache = ModelCache(revalidate=True) |         model_cache = ModelCache(revalidate=True) | ||||||
|         await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100) |         await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100) | ||||||
| @ -784,7 +783,7 @@ class TestCache: | |||||||
|         assert settings.preload.clip.visual == "ViT-B-32__openai" |         assert settings.preload.clip.visual == "ViT-B-32__openai" | ||||||
| 
 | 
 | ||||||
|         model_cache = ModelCache() |         model_cache = ModelCache() | ||||||
|         monkeypatch.setattr("app.main.model_cache", model_cache) |         monkeypatch.setattr("immich_ml.main.model_cache", model_cache) | ||||||
| 
 | 
 | ||||||
|         await preload_models(settings.preload) |         await preload_models(settings.preload) | ||||||
|         mock_get_model.assert_has_calls( |         mock_get_model.assert_has_calls( | ||||||
| @ -807,7 +806,7 @@ class TestCache: | |||||||
|         assert settings.preload.facial_recognition.recognition == "buffalo_s" |         assert settings.preload.facial_recognition.recognition == "buffalo_s" | ||||||
| 
 | 
 | ||||||
|         model_cache = ModelCache() |         model_cache = ModelCache() | ||||||
|         monkeypatch.setattr("app.main.model_cache", model_cache) |         monkeypatch.setattr("immich_ml.main.model_cache", model_cache) | ||||||
| 
 | 
 | ||||||
|         await preload_models(settings.preload) |         await preload_models(settings.preload) | ||||||
|         mock_get_model.assert_has_calls( |         mock_get_model.assert_has_calls( | ||||||
| @ -832,7 +831,7 @@ class TestCache: | |||||||
|         assert settings.preload.facial_recognition.detection == "buffalo_s" |         assert settings.preload.facial_recognition.detection == "buffalo_s" | ||||||
| 
 | 
 | ||||||
|         model_cache = ModelCache() |         model_cache = ModelCache() | ||||||
|         monkeypatch.setattr("app.main.model_cache", model_cache) |         monkeypatch.setattr("immich_ml.main.model_cache", model_cache) | ||||||
| 
 | 
 | ||||||
|         await preload_models(settings.preload) |         await preload_models(settings.preload) | ||||||
|         mock_get_model.assert_has_calls( |         mock_get_model.assert_has_calls( | ||||||
							
								
								
									
										298
									
								
								machine-learning/uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										298
									
								
								machine-learning/uv.lock
									
									
									
										generated
									
									
									
								
							| @ -927,155 +927,7 @@ wheels = [ | |||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "iniconfig" | name = "immich-ml" | ||||||
| version = "2.0.0" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "insightface" |  | ||||||
| version = "0.7.3" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| dependencies = [ |  | ||||||
|     { name = "albumentations" }, |  | ||||||
|     { name = "cython" }, |  | ||||||
|     { name = "easydict" }, |  | ||||||
|     { name = "matplotlib" }, |  | ||||||
|     { name = "numpy" }, |  | ||||||
|     { name = "onnx" }, |  | ||||||
|     { name = "pillow" }, |  | ||||||
|     { name = "prettytable" }, |  | ||||||
|     { name = "requests" }, |  | ||||||
|     { name = "scikit-image" }, |  | ||||||
|     { name = "scikit-learn" }, |  | ||||||
|     { name = "scipy" }, |  | ||||||
|     { name = "tqdm" }, |  | ||||||
| ] |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/0b/8d/0f4af90999ca96cf8cb846eb5ae27c5ef5b390f9c090dd19e4fa76364c13/insightface-0.7.3.tar.gz", hash = "sha256:f191f719612ebb37018f41936814500544cd0f86e6fcd676c023f354c668ddf7", size = 439490 } |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "itsdangerous" |  | ||||||
| version = "2.1.2" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/7f/a1/d3fb83e7a61fa0c0d3d08ad0a94ddbeff3731c05212617dff3a94e097f08/itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a", size = 56143 } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/68/5f/447e04e828f47465eeab35b5d408b7ebaaaee207f48b7136c5a7267a30ae/itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", size = 15749 }, |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "jinja2" |  | ||||||
| version = "3.1.4" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| dependencies = [ |  | ||||||
|     { name = "markupsafe" }, |  | ||||||
| ] |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "joblib" |  | ||||||
| version = "1.3.2" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/15/0f/d3b33b9f106dddef461f6df1872b7881321b247f3d255b87f61a7636f7fe/joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1", size = 1987720 } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/10/40/d551139c85db202f1f384ba8bcf96aca2f329440a844f924c8a0040b6d02/joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9", size = 302207 }, |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "kiwisolver" |  | ||||||
| version = "1.4.5" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/b9/2d/226779e405724344fc678fcc025b812587617ea1a48b9442628b688e85ea/kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec", size = 97552 } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/f1/56/cb02dcefdaab40df636b91e703b172966b444605a0ea313549f3ffc05bd3/kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af", size = 127397 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/0e/c1/d084f8edb26533a191415d5173157080837341f9a06af9dd1a75f727abb4/kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3", size = 68125 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/23/11/6fb190bae4b279d712a834e7b1da89f6dcff6791132f7399aa28a57c3565/kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4", size = 66211 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/b3/13/5e9e52feb33e9e063f76b2c5eb09cb977f5bba622df3210081bfb26ec9a3/kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1", size = 1637145 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/6f/40/4ab1fdb57fced80ce5903f04ae1aed7c1d5939dda4fd0c0aa526c12fe28a/kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff", size = 1617849 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/49/ca/61ef43bd0832c7253b370735b0c38972c140c8774889b884372a629a8189/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a", size = 1400921 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/68/6f/854f6a845c00b4257482468e08d8bc386f4929ee499206142378ba234419/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa", size = 1513009 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/50/65/76f303377167d12eb7a9b423d6771b39fe5c4373e4a42f075805b1f581ae/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c", size = 1444819 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/7e/ee/98cdf9dde129551467138b6e18cc1cc901e75ecc7ffb898c6f49609f33b1/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b", size = 1817054 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/e6/5b/ab569016ec4abc7b496f6cb8a3ab511372c99feb6a23d948cda97e0db6da/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770", size = 1918613 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/93/ac/39b9f99d2474b1ac7af1ddfe5756ddf9b6a8f24c5f3a32cd4c010317fc6b/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0", size = 1872650 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/40/5b/be568548266516b114d1776120281ea9236c732fb6032a1f8f3b1e5e921c/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525", size = 1827415 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/d4/80/c0c13d2a17a12937a19ef378bf35e94399fd171ed6ec05bcee0f038e1eaf/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b", size = 1838094 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/70/d1/5ab93ee00ca5af708929cc12fbe665b6f1ed4ad58088e70dc00e87e0d107/kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238", size = 46585 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/4a/a1/8a9c9be45c642fa12954855d8b3a02d9fd8551165a558835a19508fec2e6/kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276", size = 56095 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/2a/eb/9e099ad7c47c279995d2d20474e1821100a5f10f847739bd65b1c1f02442/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5", size = 127403 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/a6/94/695922e71288855fc7cace3bdb52edda9d7e50edba77abb0c9d7abb51e96/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90", size = 68156 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/4a/fe/23d7fa78f7c66086d196406beb1fb2eaf629dd7adc01c3453033303d17fa/kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797", size = 66166 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/f1/68/f472bf16c9141bb1bea5c0b8c66c68fc1ccb048efdbd8f0872b92125724e/kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9", size = 1334300 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/8d/26/b4569d1f29751fca22ee915b4ebfef5974f4ef239b3335fc072882bd62d9/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437", size = 1426579 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/f3/a3/804fc7c8bf233806ec0321c9da35971578620f2ab4fafe67d76100b3ce52/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9", size = 1541360 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/07/ef/286e1d26524854f6fbd6540e8364d67a8857d61038ac743e11edc42fe217/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da", size = 1470091 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/17/ba/17a706b232308e65f57deeccae503c268292e6a091313f6ce833a23093ea/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e", size = 1426259 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/d0/f3/a0925611c9d6c2f37c5935a39203cadec6883aa914e013b46c84c4c2e641/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8", size = 1847516 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/da/85/82d59bb8f7c4c9bb2785138b72462cb1b161668f8230c58bbb28c0403cd5/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d", size = 1946228 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/34/3c/6a37f444c0233993881e5db3a6a1775925d4d9d2f2609bb325bb1348ed94/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0", size = 1901716 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/cd/7e/180425790efc00adfd47db14e1e341cb4826516982334129012b971121a6/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f", size = 1852871 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/1b/9a/13c68b2edb1fa74321e60893a9a5829788e135138e68060cf44e2d92d2c3/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f", size = 1870265 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/9f/0a/fa56a0fdee5da2b4c79899c0f6bd1aefb29d9438c2d66430e78793571c6b/kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac", size = 46649 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/1e/37/d3c2d4ba2719059a0f12730947bbe1ad5ee8bff89e8c35319dcb2c9ddb4c/kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355", size = 56116 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/f3/7a/debbce859be1a2711eb8437818107137192007b88d17b5cfdb556f457b42/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a", size = 125484 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/2d/e0/bf8df75ba93b9e035cc6757dd5dcaf63084fdc1c846ae134e818bd7e0f03/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192", size = 67332 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/26/61/58bb691f6880588be3a4801d199bd776032ece07203faf3e4a8b377f7d9b/kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45", size = 64987 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/8e/a3/96ac5413068b237c006f54dd8d70114e8756d70e3da7613c5aef20627e22/kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7", size = 1370613 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/4d/12/f48539e6e17068b59c7f12f4d6214b973431b8e3ac83af525cafd27cebec/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db", size = 1463183 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/f3/70/26c99be8eb034cc8e3f62e0760af1fbdc97a842a7cbc252f7978507d41c2/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff", size = 1581248 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/17/f6/f75f20e543639b09b2de7fc864274a5a9b96cda167a6210a1d9d19306b9d/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228", size = 1508815 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/e3/d5/bc0f22ac108743062ab703f8d6d71c9c7b077b8839fa358700bfb81770b8/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16", size = 1466042 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/75/18/98142500f21d6838bcab49ec919414a1f0c6d049d21ddadf139124db6a70/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9", size = 1885159 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/21/49/a241eff9e0ee013368c1d17957f9d345b0957493c3a43d82ebb558c90b0a/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162", size = 1981694 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/90/90/9490c3de4788123041b1d600d64434f1eed809a2ce9f688075a22166b289/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4", size = 1941579 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/b7/bb/a0cc488ef2aa92d7d304318c8549d3ec8dfe6dd3c2c67a44e3922b77bc4f/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3", size = 1888168 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/4f/e9/9c0de8e45fef3d63f85eed3b1757f9aa511065942866331ef8b99421f433/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a", size = 1908464 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/a3/60/4f0fd50b08f5be536ea0cef518ac7255d9dab43ca40f3b93b60e3ddf80dd/kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20", size = 46473 }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/63/50/2746566bdf4a6a842d117367d05c90cfb87ac04e9e2845aa1fa21f071362/kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9", size = 56004 }, |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "lazy-loader" |  | ||||||
| version = "0.3" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/0e/3a/1630a735bfdf9eb857a3b9a53317a1e1658ea97a1b4b39dcb0f71dae81f8/lazy_loader-0.3.tar.gz", hash = "sha256:3b68898e34f5b2a29daaaac172c6555512d0f32074f147e2254e4a6d9d838f37", size = 12268 } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/a1/c3/65b3814e155836acacf720e5be3b5757130346670ac454fee29d3eda1381/lazy_loader-0.3-py3-none-any.whl", hash = "sha256:1e9e76ee8631e264c62ce10006718e80b2cfc74340d17d1031e0f84af7478554", size = 9087 }, |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "locust" |  | ||||||
| version = "2.33.2" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| dependencies = [ |  | ||||||
|     { name = "configargparse" }, |  | ||||||
|     { name = "flask" }, |  | ||||||
|     { name = "flask-cors" }, |  | ||||||
|     { name = "flask-login" }, |  | ||||||
|     { name = "gevent", marker = "python_full_version != '3.13.*'" }, |  | ||||||
|     { name = "geventhttpclient" }, |  | ||||||
|     { name = "msgpack" }, |  | ||||||
|     { name = "psutil" }, |  | ||||||
|     { name = "pywin32", marker = "sys_platform == 'win32'" }, |  | ||||||
|     { name = "pyzmq" }, |  | ||||||
|     { name = "requests" }, |  | ||||||
|     { name = "setuptools" }, |  | ||||||
|     { name = "tomli", marker = "python_full_version < '3.11'" }, |  | ||||||
|     { name = "typing-extensions", marker = "python_full_version < '3.11'" }, |  | ||||||
|     { name = "werkzeug" }, |  | ||||||
| ] |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/a2/9e/09ee87dc12b240248731080bfd460c7d384aadb3171f6d03a4e7314cd0e1/locust-2.33.2.tar.gz", hash = "sha256:e626ed0156f36cec94c3c6b030fc91046469e7e2f5c2e91a99aab0f28b84977e", size = 2237716 } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/9c/c7/bb55ac53173d3e92b1b2577d0f36439500406ca5be476a27b7bc01ae8a75/locust-2.33.2-py3-none-any.whl", hash = "sha256:a2f3b53dcd5ed22cecee874cd989912749663d82ec9b030637d3e43044e5878e", size = 2254591 }, |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "machine-learning" |  | ||||||
| version = "1.129.0" | version = "1.129.0" | ||||||
| source = { editable = "." } | source = { editable = "." } | ||||||
| dependencies = [ | dependencies = [ | ||||||
| @ -1224,6 +1076,154 @@ types = [ | |||||||
|     { name = "types-ujson", specifier = ">=5.10.0.20240515" }, |     { name = "types-ujson", specifier = ">=5.10.0.20240515" }, | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "iniconfig" | ||||||
|  | version = "2.0.0" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "insightface" | ||||||
|  | version = "0.7.3" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "albumentations" }, | ||||||
|  |     { name = "cython" }, | ||||||
|  |     { name = "easydict" }, | ||||||
|  |     { name = "matplotlib" }, | ||||||
|  |     { name = "numpy" }, | ||||||
|  |     { name = "onnx" }, | ||||||
|  |     { name = "pillow" }, | ||||||
|  |     { name = "prettytable" }, | ||||||
|  |     { name = "requests" }, | ||||||
|  |     { name = "scikit-image" }, | ||||||
|  |     { name = "scikit-learn" }, | ||||||
|  |     { name = "scipy" }, | ||||||
|  |     { name = "tqdm" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/0b/8d/0f4af90999ca96cf8cb846eb5ae27c5ef5b390f9c090dd19e4fa76364c13/insightface-0.7.3.tar.gz", hash = "sha256:f191f719612ebb37018f41936814500544cd0f86e6fcd676c023f354c668ddf7", size = 439490 } | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "itsdangerous" | ||||||
|  | version = "2.1.2" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/7f/a1/d3fb83e7a61fa0c0d3d08ad0a94ddbeff3731c05212617dff3a94e097f08/itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a", size = 56143 } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/68/5f/447e04e828f47465eeab35b5d408b7ebaaaee207f48b7136c5a7267a30ae/itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", size = 15749 }, | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "jinja2" | ||||||
|  | version = "3.1.4" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "markupsafe" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "joblib" | ||||||
|  | version = "1.3.2" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/15/0f/d3b33b9f106dddef461f6df1872b7881321b247f3d255b87f61a7636f7fe/joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1", size = 1987720 } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/10/40/d551139c85db202f1f384ba8bcf96aca2f329440a844f924c8a0040b6d02/joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9", size = 302207 }, | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "kiwisolver" | ||||||
|  | version = "1.4.5" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/b9/2d/226779e405724344fc678fcc025b812587617ea1a48b9442628b688e85ea/kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec", size = 97552 } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/f1/56/cb02dcefdaab40df636b91e703b172966b444605a0ea313549f3ffc05bd3/kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af", size = 127397 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/0e/c1/d084f8edb26533a191415d5173157080837341f9a06af9dd1a75f727abb4/kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3", size = 68125 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/23/11/6fb190bae4b279d712a834e7b1da89f6dcff6791132f7399aa28a57c3565/kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4", size = 66211 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/b3/13/5e9e52feb33e9e063f76b2c5eb09cb977f5bba622df3210081bfb26ec9a3/kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1", size = 1637145 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/6f/40/4ab1fdb57fced80ce5903f04ae1aed7c1d5939dda4fd0c0aa526c12fe28a/kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff", size = 1617849 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/49/ca/61ef43bd0832c7253b370735b0c38972c140c8774889b884372a629a8189/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a", size = 1400921 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/68/6f/854f6a845c00b4257482468e08d8bc386f4929ee499206142378ba234419/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa", size = 1513009 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/50/65/76f303377167d12eb7a9b423d6771b39fe5c4373e4a42f075805b1f581ae/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c", size = 1444819 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/7e/ee/98cdf9dde129551467138b6e18cc1cc901e75ecc7ffb898c6f49609f33b1/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b", size = 1817054 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/e6/5b/ab569016ec4abc7b496f6cb8a3ab511372c99feb6a23d948cda97e0db6da/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770", size = 1918613 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/93/ac/39b9f99d2474b1ac7af1ddfe5756ddf9b6a8f24c5f3a32cd4c010317fc6b/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0", size = 1872650 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/40/5b/be568548266516b114d1776120281ea9236c732fb6032a1f8f3b1e5e921c/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525", size = 1827415 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/d4/80/c0c13d2a17a12937a19ef378bf35e94399fd171ed6ec05bcee0f038e1eaf/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b", size = 1838094 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/70/d1/5ab93ee00ca5af708929cc12fbe665b6f1ed4ad58088e70dc00e87e0d107/kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238", size = 46585 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/4a/a1/8a9c9be45c642fa12954855d8b3a02d9fd8551165a558835a19508fec2e6/kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276", size = 56095 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/2a/eb/9e099ad7c47c279995d2d20474e1821100a5f10f847739bd65b1c1f02442/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5", size = 127403 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/a6/94/695922e71288855fc7cace3bdb52edda9d7e50edba77abb0c9d7abb51e96/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90", size = 68156 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/4a/fe/23d7fa78f7c66086d196406beb1fb2eaf629dd7adc01c3453033303d17fa/kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797", size = 66166 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/f1/68/f472bf16c9141bb1bea5c0b8c66c68fc1ccb048efdbd8f0872b92125724e/kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9", size = 1334300 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/8d/26/b4569d1f29751fca22ee915b4ebfef5974f4ef239b3335fc072882bd62d9/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437", size = 1426579 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/f3/a3/804fc7c8bf233806ec0321c9da35971578620f2ab4fafe67d76100b3ce52/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9", size = 1541360 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/07/ef/286e1d26524854f6fbd6540e8364d67a8857d61038ac743e11edc42fe217/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da", size = 1470091 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/17/ba/17a706b232308e65f57deeccae503c268292e6a091313f6ce833a23093ea/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e", size = 1426259 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/d0/f3/a0925611c9d6c2f37c5935a39203cadec6883aa914e013b46c84c4c2e641/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8", size = 1847516 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/da/85/82d59bb8f7c4c9bb2785138b72462cb1b161668f8230c58bbb28c0403cd5/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d", size = 1946228 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/34/3c/6a37f444c0233993881e5db3a6a1775925d4d9d2f2609bb325bb1348ed94/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0", size = 1901716 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/cd/7e/180425790efc00adfd47db14e1e341cb4826516982334129012b971121a6/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f", size = 1852871 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/1b/9a/13c68b2edb1fa74321e60893a9a5829788e135138e68060cf44e2d92d2c3/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f", size = 1870265 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/9f/0a/fa56a0fdee5da2b4c79899c0f6bd1aefb29d9438c2d66430e78793571c6b/kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac", size = 46649 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/1e/37/d3c2d4ba2719059a0f12730947bbe1ad5ee8bff89e8c35319dcb2c9ddb4c/kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355", size = 56116 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/f3/7a/debbce859be1a2711eb8437818107137192007b88d17b5cfdb556f457b42/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a", size = 125484 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/2d/e0/bf8df75ba93b9e035cc6757dd5dcaf63084fdc1c846ae134e818bd7e0f03/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192", size = 67332 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/26/61/58bb691f6880588be3a4801d199bd776032ece07203faf3e4a8b377f7d9b/kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45", size = 64987 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/8e/a3/96ac5413068b237c006f54dd8d70114e8756d70e3da7613c5aef20627e22/kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7", size = 1370613 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/4d/12/f48539e6e17068b59c7f12f4d6214b973431b8e3ac83af525cafd27cebec/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db", size = 1463183 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/f3/70/26c99be8eb034cc8e3f62e0760af1fbdc97a842a7cbc252f7978507d41c2/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff", size = 1581248 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/17/f6/f75f20e543639b09b2de7fc864274a5a9b96cda167a6210a1d9d19306b9d/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228", size = 1508815 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/e3/d5/bc0f22ac108743062ab703f8d6d71c9c7b077b8839fa358700bfb81770b8/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16", size = 1466042 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/75/18/98142500f21d6838bcab49ec919414a1f0c6d049d21ddadf139124db6a70/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9", size = 1885159 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/21/49/a241eff9e0ee013368c1d17957f9d345b0957493c3a43d82ebb558c90b0a/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162", size = 1981694 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/90/90/9490c3de4788123041b1d600d64434f1eed809a2ce9f688075a22166b289/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4", size = 1941579 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/b7/bb/a0cc488ef2aa92d7d304318c8549d3ec8dfe6dd3c2c67a44e3922b77bc4f/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3", size = 1888168 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/4f/e9/9c0de8e45fef3d63f85eed3b1757f9aa511065942866331ef8b99421f433/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a", size = 1908464 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/a3/60/4f0fd50b08f5be536ea0cef518ac7255d9dab43ca40f3b93b60e3ddf80dd/kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20", size = 46473 }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/63/50/2746566bdf4a6a842d117367d05c90cfb87ac04e9e2845aa1fa21f071362/kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9", size = 56004 }, | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "lazy-loader" | ||||||
|  | version = "0.3" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/0e/3a/1630a735bfdf9eb857a3b9a53317a1e1658ea97a1b4b39dcb0f71dae81f8/lazy_loader-0.3.tar.gz", hash = "sha256:3b68898e34f5b2a29daaaac172c6555512d0f32074f147e2254e4a6d9d838f37", size = 12268 } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/a1/c3/65b3814e155836acacf720e5be3b5757130346670ac454fee29d3eda1381/lazy_loader-0.3-py3-none-any.whl", hash = "sha256:1e9e76ee8631e264c62ce10006718e80b2cfc74340d17d1031e0f84af7478554", size = 9087 }, | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "locust" | ||||||
|  | version = "2.33.2" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "configargparse" }, | ||||||
|  |     { name = "flask" }, | ||||||
|  |     { name = "flask-cors" }, | ||||||
|  |     { name = "flask-login" }, | ||||||
|  |     { name = "gevent", marker = "python_full_version != '3.13.*'" }, | ||||||
|  |     { name = "geventhttpclient" }, | ||||||
|  |     { name = "msgpack" }, | ||||||
|  |     { name = "psutil" }, | ||||||
|  |     { name = "pywin32", marker = "sys_platform == 'win32'" }, | ||||||
|  |     { name = "pyzmq" }, | ||||||
|  |     { name = "requests" }, | ||||||
|  |     { name = "setuptools" }, | ||||||
|  |     { name = "tomli", marker = "python_full_version < '3.11'" }, | ||||||
|  |     { name = "typing-extensions", marker = "python_full_version < '3.11'" }, | ||||||
|  |     { name = "werkzeug" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/a2/9e/09ee87dc12b240248731080bfd460c7d384aadb3171f6d03a4e7314cd0e1/locust-2.33.2.tar.gz", hash = "sha256:e626ed0156f36cec94c3c6b030fc91046469e7e2f5c2e91a99aab0f28b84977e", size = 2237716 } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/9c/c7/bb55ac53173d3e92b1b2577d0f36439500406ca5be476a27b7bc01ae8a75/locust-2.33.2-py3-none-any.whl", hash = "sha256:a2f3b53dcd5ed22cecee874cd989912749663d82ec9b030637d3e43044e5878e", size = 2254591 }, | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "markdown-it-py" | name = "markdown-it-py" | ||||||
| version = "3.0.0" | version = "3.0.0" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user