diff --git a/wavefront/server/apps/floconsole/pyproject.toml b/wavefront/server/apps/floconsole/pyproject.toml index f562f764..8c46280a 100644 --- a/wavefront/server/apps/floconsole/pyproject.toml +++ b/wavefront/server/apps/floconsole/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "sqlalchemy>=2.0.40,<3.0.0", "python-dotenv>=1.1.0,<2.0.0", "dependency-injector>=4.46.0,<5.0.0", - "psycopg2>=2.9.10,<3.0.0", + "psycopg2-binary>=2.9.10,<3.0.0", "python-jose[cryptography]>=3.3.0,<4.0.0", "async-lru>=2.0.5", ] diff --git a/wavefront/server/apps/inference_app/inference_app/config.ini b/wavefront/server/apps/inference_app/inference_app/config.ini index cc12d9df..8b137891 100644 --- a/wavefront/server/apps/inference_app/inference_app/config.ini +++ b/wavefront/server/apps/inference_app/inference_app/config.ini @@ -1,8 +1 @@ -[aws] -model_storage_bucket=${MODEL_STORAGE_BUCKET} -[gcp] -model_storage_bucket=${MODEL_STORAGE_BUCKET} - -[cloud_config] -cloud_provider=${CLOUD_PROVIDER} diff --git a/wavefront/server/apps/inference_app/inference_app/controllers/inference_controller.py b/wavefront/server/apps/inference_app/inference_app/controllers/inference_controller.py index e9a5e82a..5a90c435 100644 --- a/wavefront/server/apps/inference_app/inference_app/controllers/inference_controller.py +++ b/wavefront/server/apps/inference_app/inference_app/controllers/inference_controller.py @@ -1,156 +1,58 @@ import base64 -from typing import Any, Dict +import binascii from common_module.common_container import CommonContainer -from common_module.log.logger import logger from common_module.response_formatter import ResponseFormatter from dependency_injector.wiring import Provide, inject from fastapi import APIRouter, Depends, status from fastapi.responses import JSONResponse from inference_app.inference_app_container import InferenceAppContainer -from inference_app.service.image_analyser import ImageClarityService -from inference_app.service.model_inference import ( - ModelInferenceService, - PreprocessingStep, -) -from inference_app.service.model_repository import ModelRepository from inference_app.service.image_embedding import ImageEmbedding -from pydantic import BaseModel, Field +from pydantic import BaseModel -class InferencePayload(BaseModel): - data: str - payload_type: str - model_info: dict - preprocessing_steps: list[PreprocessingStep] - max_expected_variance: int = Field(default=1000) - resize_width: int = Field(default=224) - resize_height: int = Field(default=224) - gaussian_blur_kernel: int = Field(default=3) - min_threshold: int = Field(default=50) - max_threshold: int = Field(default=150) - normalize_mean: str = Field(default='0.485,0.456,0.406') - normalize_std: str = Field(default='0.229,0.224,0.225') - - -class InferenceResult(BaseModel): - results: Dict[str, Any] = Field(..., description='Dictionary of inference results') +class ImagePayload(BaseModel): + image_data: str # base64 encoded image data -class ImagePayload(BaseModel): - image_data: str +class ImageBatchPayload(BaseModel): + image_batch: list[str] # list of base64 encoded image data inference_app_router = APIRouter() -@inference_app_router.post('/v1/model-repository/model/{model_id}/infer') +@inference_app_router.post('/v1/query/embeddings') @inject -async def generic_inference_handler( - payload: InferencePayload, +async def image_embedding( + payload: ImagePayload, response_formatter: ResponseFormatter = Depends( Provide[CommonContainer.response_formatter] ), - model_repository: ModelRepository = Depends( - Provide[InferenceAppContainer.model_repository] - ), - image_analyser: ImageClarityService = Depends( - Provide[InferenceAppContainer.image_analyser] - ), - config: dict = Depends(Provide[InferenceAppContainer.config]), - model_inference: ModelInferenceService = Depends( - Provide[InferenceAppContainer.model_inference] + image_embedding_service: ImageEmbedding = Depends( + Provide[InferenceAppContainer.image_embedding] ), ): - try: - provider = config['cloud_config']['cloud_provider'] - model_storage_bucket = ( - config['gcp']['model_storage_bucket'] - if provider.lower() == 'gcp' - else config['aws']['model_storage_bucket'] - ) - - logger.info( - f'Loading model from bucket: {model_storage_bucket}, model_info: {payload.model_info}' - ) - model = await model_repository.load_model( - model_info=payload.model_info, bucket_name=model_storage_bucket - ) - logger.debug('Model loaded successfully for model_id') - - if payload.payload_type.lower() == 'image': - base64_data_uri = payload.data - parts = base64_data_uri.split(',') - if len(parts) == 2: - base64_data = parts[1] - image_bytes = base64.b64decode(base64_data) - - clarity_score = image_analyser.laplacian_detection( - image_bytes, payload.max_expected_variance - ) - - infer_data = model_inference.model_infer_score( - model, - image_bytes, - payload.resize_width, - payload.resize_height, - payload.normalize_mean, - payload.normalize_std, - payload.gaussian_blur_kernel, - payload.min_threshold, - payload.max_threshold, - preprocessing_steps=payload.preprocessing_steps, - ) - logger.debug('Model inference completed successfully for model_id') - - inference_results = InferenceResult( - results={ - 'clarity_score': clarity_score, - 'infer_data': infer_data, - 'data_type': payload.payload_type.lower(), - } - ) - - logger.info('Inference request completed successfully for model_id') - return JSONResponse( - status_code=status.HTTP_201_CREATED, - content=response_formatter.buildSuccessResponse( - inference_results.dict() - ), - ) - else: - error_msg = ( - "Input data is not in expected Data URI format (missing 'base64,')." - ) - logger.error( - f"Expected Data URI format with 'base64,' prefix. " - f'Data length: {len(base64_data_uri)}' - ) - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content=response_formatter.buildErrorResponse(error_msg), - ) - else: - error_msg = f"Invalid payload_type: {payload.payload_type}. Accepted values are 'image'" - logger.error(f'{error_msg}') - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content=response_formatter.buildErrorResponse( - 'Invalid payload_type. Accepted values are "image"' - ), - ) - except Exception as e: - logger.error(f'Error in generic_inference_handler {str(e)}') + # 1. Decode Base64 string + image_data = extract_decoded_image_data(payload.image_data) + embeddings = image_embedding_service.query_embed(image_data) + if not embeddings: return JSONResponse( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content=response_formatter.buildErrorResponse('Internal server error'), + status_code=status.HTTP_400_BAD_REQUEST, + content=response_formatter.buildErrorResponse( + 'No Embedding data is present' + ), ) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=response_formatter.buildSuccessResponse(data={'response': embeddings}), + ) -@inference_app_router.post('/v1/query/embeddings') +@inference_app_router.post('/v1/query/embeddings/batch') @inject -async def image_embedding( - payload: ImagePayload, +async def image_embedding_batch( + payload: ImageBatchPayload, response_formatter: ResponseFormatter = Depends( Provide[CommonContainer.response_formatter] ), @@ -158,12 +60,18 @@ async def image_embedding( Provide[InferenceAppContainer.image_embedding] ), ): - # 1. Decode Base64 string - base64_data_uri = payload.image_data - parts = base64_data_uri.split(',') - base64_data = parts[1] if len(parts) == 2 else parts[0] - image_data = base64.b64decode(base64_data) - embeddings = image_embedding_service.query_embed(image_data) + try: + image_batch = [ + extract_decoded_image_data(image_data) for image_data in payload.image_batch + ] + except binascii.Error: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content=response_formatter.buildErrorResponse( + 'Invalid base64 image data in batch' + ), + ) + embeddings = image_embedding_service.query_embed_batch(image_batch) if not embeddings: return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, @@ -175,3 +83,9 @@ async def image_embedding( status_code=status.HTTP_200_OK, content=response_formatter.buildSuccessResponse(data={'response': embeddings}), ) + + +def extract_decoded_image_data(image_data: str) -> bytes: + parts = image_data.split(',') + base64_data = parts[1] if len(parts) == 2 else parts[0] + return base64.b64decode(base64_data) diff --git a/wavefront/server/apps/inference_app/inference_app/inference_app_container.py b/wavefront/server/apps/inference_app/inference_app/inference_app_container.py index 8666dcbe..b0fd7ea8 100644 --- a/wavefront/server/apps/inference_app/inference_app/inference_app_container.py +++ b/wavefront/server/apps/inference_app/inference_app/inference_app_container.py @@ -1,31 +1,9 @@ from dependency_injector import containers from dependency_injector import providers -from inference_app.service.image_analyser import ImageClarityService -from flo_cloud.cloud_storage import CloudStorageManager -from inference_app.service.model_repository import ModelRepository -from inference_app.service.model_inference import ModelInferenceService from inference_app.service.image_embedding import ImageEmbedding class InferenceAppContainer(containers.DeclarativeContainer): config = providers.Configuration(ini_files=['config.ini']) - cache_manager = providers.Dependency() - cloud_storage_manager = providers.Singleton( - CloudStorageManager, provider=config.cloud_config.cloud_provider - ) - - model_repository = providers.Singleton( - ModelRepository, - cloud_storage_manager=cloud_storage_manager, - ) - - model_inference = providers.Singleton(ModelInferenceService) - - image_analyser = providers.Singleton( - ImageClarityService, - ) - - image_embedding = providers.Singleton( - ImageEmbedding, - ) + image_embedding = providers.Singleton(ImageEmbedding) diff --git a/wavefront/server/apps/inference_app/inference_app/server.py b/wavefront/server/apps/inference_app/inference_app/server.py index 10274e65..b0d73530 100644 --- a/wavefront/server/apps/inference_app/inference_app/server.py +++ b/wavefront/server/apps/inference_app/inference_app/server.py @@ -1,5 +1,6 @@ import glob import os +from contextlib import asynccontextmanager from dotenv import load_dotenv from fastapi import FastAPI @@ -22,14 +23,22 @@ # Initialize dependency containers common_container = CommonContainer(cache_manager=None) -inference_app_container = InferenceAppContainer( - cache_manager=None, -) +inference_app_container = InferenceAppContainer() + + +@asynccontextmanager +async def lifespan(app: FastAPI): + logger.info('Preloading ML models...') + inference_app_container.image_embedding() + logger.info('ML models loaded and ready.') + yield + app = FastAPI( title='FloConsole API', description='Console application for RootFlo platform', version='1.0.0', + lifespan=lifespan, ) diff --git a/wavefront/server/apps/inference_app/inference_app/service/image_analyser.py b/wavefront/server/apps/inference_app/inference_app/service/image_analyser.py deleted file mode 100644 index e7a89f0a..00000000 --- a/wavefront/server/apps/inference_app/inference_app/service/image_analyser.py +++ /dev/null @@ -1,22 +0,0 @@ -import cv2 -from common_module.log.logger import logger -from inference_app.utils.image_utils import decode_image_from_bytes - - -class ImageClarityService: - def __init__(self): - pass - - def laplacian_detection(self, image_bytes, max_expected_variance): - # Decode image from bytes array - logger.info( - f'Successfully decoded Base64 string. Data length: {len(image_bytes)} bytes.' - ) - images = decode_image_from_bytes(image_bytes) - images = cv2.resize(images, (256, 256), interpolation=cv2.INTER_AREA) - gray = cv2.cvtColor(images, cv2.COLOR_BGR2GRAY) - laplacian = cv2.Laplacian(gray, cv2.CV_64F) - variance = laplacian.var() - clamped_variance = min(variance, int(max_expected_variance)) - score = (clamped_variance / int(max_expected_variance)) * 100 - return int(score) diff --git a/wavefront/server/apps/inference_app/inference_app/service/image_embedding.py b/wavefront/server/apps/inference_app/inference_app/service/image_embedding.py index 106eba37..659d769d 100644 --- a/wavefront/server/apps/inference_app/inference_app/service/image_embedding.py +++ b/wavefront/server/apps/inference_app/inference_app/service/image_embedding.py @@ -5,24 +5,24 @@ from typing import List, Dict, Any from common_module.log.logger import logger +CLIP_MODEL_NAME = 'openai/clip-vit-base-patch32' +DINO_MODEL_NAME = 'facebook/dinov3-vitl16-pretrain-lvd1689m' -class ImageEmbedding: - CLIP_MODEL_NAME = 'openai/clip-vit-base-patch32' - DINO_MODEL_NAME = 'facebook/dinov3-vitl16-pretrain-lvd1689m' +class ImageEmbedding: def __init__(self): self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') logger.info(f'Using device: {self.device}') - self.clip_processor = CLIPProcessor.from_pretrained(self.CLIP_MODEL_NAME) - self.clip_model = CLIPModel.from_pretrained(self.CLIP_MODEL_NAME).to( + self.clip_processor = CLIPProcessor.from_pretrained(CLIP_MODEL_NAME) + self.clip_model = CLIPModel.from_pretrained(CLIP_MODEL_NAME).to( self.device ) self.clip_model.eval() - self.dino_processor = AutoImageProcessor.from_pretrained(self.DINO_MODEL_NAME) + self.dino_processor = AutoImageProcessor.from_pretrained(DINO_MODEL_NAME) self.dino_model = AutoModel.from_pretrained( - self.DINO_MODEL_NAME, trust_remote_code=True + DINO_MODEL_NAME, trust_remote_code=True ).to(self.device) self.dino_model.eval() @@ -69,3 +69,51 @@ def query_embed(self, image_content: bytes) -> List[Dict[str, List[float]]]: results.append({name: embedding}) return results + + @torch.inference_mode() + def query_embed_batch( + self, image_batch: list[bytes] + ) -> List[Dict[str, List[List[float]]]]: + """ + GPU batch embedding. + + Returns: + [ + {"clip": [embedding_for_image_0, ..., embedding_for_image_N]}, + {"dino": [embedding_for_image_0, ..., embedding_for_image_N]}, + ] + """ + if not image_batch: + return [] + + # Decode bytes -> PIL images on CPU. + # The actual model forward pass (processor->tensor + model) is batched on GPU. + images: List[Image.Image] = [] + for idx, image_content in enumerate(image_batch): + try: + images.append(Image.open(io.BytesIO(image_content)).convert('RGB')) + except Exception as e: + logger.error( + f'Error opening image at index={idx}: {e}', + exc_info=True, + ) + raise ValueError( + f'Failed to decode image at index {idx}: {e}' + ) from e + + results: List[Dict[str, List[List[float]]]] = [] + + for name, embedder in self.embedders.items(): + inputs = embedder['processor'](images=images, return_tensors='pt') + inputs = {k: v.to(self.device) for k, v in inputs.items()} + + # Batched forward pass. + image_features = embedder['extractor'](inputs) # (batch, dim) + + # L2-normalize per-vector. + image_features = image_features / image_features.norm(dim=-1, keepdim=True) + + embeddings = image_features.cpu().numpy().tolist() # batch x dim + results.append({name: embeddings}) + + return results diff --git a/wavefront/server/apps/inference_app/inference_app/service/model_inference.py b/wavefront/server/apps/inference_app/inference_app/service/model_inference.py deleted file mode 100644 index 929ccf3e..00000000 --- a/wavefront/server/apps/inference_app/inference_app/service/model_inference.py +++ /dev/null @@ -1,99 +0,0 @@ -import cv2 -import torchvision.transforms as transforms -from PIL import Image -import torch -import numpy as np -from pydantic import BaseModel, Field -from inference_app.utils.image_utils import decode_image_from_bytes - - -class PreprocessingStep(BaseModel): - preprocess_filter: str - values: list = Field(default_factory=list) - - -class ModelInferenceService: - def __init__(self): - self.device = torch.device('cpu') - - def preprocess_image( - self, - image_bytes, - gaussian_blur_kernel, - min_threshold, - max_threshold, - preprocessing_steps: list[PreprocessingStep], - ): - """Apply preprocessing steps based on provided flags.""" - processed_image = decode_image_from_bytes(image_bytes) - - # Define available preprocessing functions - preprocessing_functions = { - 'gray': lambda img, values: cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), - 'gaussian_blur': lambda img, values: cv2.GaussianBlur( - img, (gaussian_blur_kernel, gaussian_blur_kernel), 0 - ), - 'canny': lambda img, values: cv2.cvtColor( - cv2.Canny(img, min_threshold, max_threshold), cv2.COLOR_GRAY2RGB - ), - 'kernel_sharpening': lambda img, values: cv2.filter2D( - img, -1, np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]]) - ), - } - for step in preprocessing_steps: - filter_name = step.preprocess_filter - values = step.values - if filter_name and filter_name in preprocessing_functions: - processed_image = preprocessing_functions[filter_name]( - processed_image, values - ) - else: - continue - - pil_image = Image.fromarray(processed_image) - return pil_image - - def model_infer_score( - self, - model, - image_bytes, - resize_width, - resize_height, - normalize_mean, - normalize_std, - gaussian_blur_kernel, - min_threshold, - max_threshold, - preprocessing_steps: list[PreprocessingStep], - ): - """ - Predict overlap score for a single image using the same preprocessing as training - """ - # Define the same transform used during validation - normalize_mean = [float(x) for x in normalize_mean.split(',')] - normalize_std = [float(x) for x in normalize_std.split(',')] - transform = transforms.Compose( - [ - transforms.Resize((resize_width, resize_height)), - transforms.ToTensor(), - transforms.Normalize(mean=normalize_mean, std=normalize_std), - ] - ) - # Apply the same preprocessing as during training - preprocessed_image = self.preprocess_image( - image_bytes, - gaussian_blur_kernel, - min_threshold, - max_threshold, - preprocessing_steps, - ) - - # Apply transforms - image_tensor = transform(preprocessed_image).unsqueeze(0).to(self.device) - model.to(self.device) - # Predict - model.eval() - with torch.no_grad(): - response = model(image_tensor).item() - - return response diff --git a/wavefront/server/apps/inference_app/inference_app/service/model_repository.py b/wavefront/server/apps/inference_app/inference_app/service/model_repository.py deleted file mode 100644 index 831dc8a0..00000000 --- a/wavefront/server/apps/inference_app/inference_app/service/model_repository.py +++ /dev/null @@ -1,68 +0,0 @@ -from typing import Dict -import os -import torch -import io -from common_module.log.logger import logger -from flo_cloud.cloud_storage import CloudStorageManager - - -class ModelRepository: - def __init__( - self, - cloud_storage_manager: CloudStorageManager, - ): - self.cloud_storage_manager = cloud_storage_manager - self.model_storage_dir = os.getenv('MODEL_STORAGE_DIR', './models') - os.makedirs(self.model_storage_dir, exist_ok=True) - # Cache for loaded models - stores model instances in memory - self._model_cache: Dict[str, torch.nn.Module] = {} - - def _is_model_cached_locally( - self, model_name: str, file_path: str, expected_local_model_dir: str - ) -> bool: - """ - Checks if the model is available in the local persistent storage. - """ - return os.path.exists( - expected_local_model_dir - ) and f'{model_name}.{file_path.split(".")[-1]}' in os.listdir( - expected_local_model_dir - ) - - async def load_model(self, model_info: dict, bucket_name: str): - model_id = model_info['model_id'] - expected_local_model_dir = self.model_storage_dir - model_name = model_info['model_name'] - file_path = model_info['model_path'] - model_id = model_info['model_id'] - - local_model_filename = os.path.join( - expected_local_model_dir, f'{model_name}.{file_path.split(".")[1]}' - ) - local_model_full_path = os.path.join(local_model_filename) - - if self._is_model_cached_locally( - model_name, file_path, expected_local_model_dir - ): - logger.info(f'Model {model_id} found in local persistent storage, loading.') - if model_id in self._model_cache: - return self._model_cache[model_id] - else: - with open(local_model_full_path, 'rb') as f: - model_bytes_data = f.read() - return torch.load(io.BytesIO(model_bytes_data), weights_only=False) - else: - logger.info( - f'Model {model_id} not found in local persistent storage, loading from cloud storage.' - ) - model_bytes_data = self.cloud_storage_manager.read_file( - bucket_name, file_path - ) - model = torch.load(io.BytesIO(model_bytes_data), weights_only=False) - # Save to local persistent storage after fetching from cloud - os.makedirs(os.path.dirname(local_model_full_path), exist_ok=True) - with open(local_model_full_path, 'wb') as f: - f.write(model_bytes_data) - self._model_cache[model_id] = model - logger.info(f'Model {model_id} loaded and cached in memory.') - return model diff --git a/wavefront/server/apps/inference_app/pyproject.toml b/wavefront/server/apps/inference_app/pyproject.toml index 977aefc8..a914e117 100644 --- a/wavefront/server/apps/inference_app/pyproject.toml +++ b/wavefront/server/apps/inference_app/pyproject.toml @@ -18,7 +18,7 @@ dependencies = [ "torchvision==0.16.0", "opencv-python>=4.11.0.86", "pillow>=11.1.0,<12", - "psycopg2>=2.9.10,<3.0.0", + "psycopg2-binary>=2.9.10,<3.0.0", "numpy>=1.26.4,<2.0.0", "accelerate>=0.33.0,<1.0.0", "transformers>=4.45.0" diff --git a/wavefront/server/modules/insights_module/pyproject.toml b/wavefront/server/modules/insights_module/pyproject.toml index 487a36f6..4a2f8c90 100644 --- a/wavefront/server/modules/insights_module/pyproject.toml +++ b/wavefront/server/modules/insights_module/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "pyyaml>=6.0.3,<7", "dependency-injector>=4.42.0,<5.0.0", "redshift-connector>=2.1.5,<3.0.0", - "psycopg2>=2.9.10,<3.0.0", + "psycopg2-binary>=2.9.10,<3.0.0", "fastapi>=0.115.2,<1.0.0", "dacite>=1.9.2,<2.0.0", "httpx>=0.28.1,<1.0.0", diff --git a/wavefront/server/uv.lock b/wavefront/server/uv.lock index f7bf7aa7..07c7e11e 100644 --- a/wavefront/server/uv.lock +++ b/wavefront/server/uv.lock @@ -1498,7 +1498,7 @@ dependencies = [ { name = "flo-cloud" }, { name = "httpx" }, { name = "psycopg", extra = ["binary", "pool"] }, - { name = "psycopg2" }, + { name = "psycopg2-binary" }, { name = "python-dotenv" }, { name = "python-jose", extra = ["cryptography"] }, { name = "sqlalchemy" }, @@ -1516,7 +1516,7 @@ requires-dist = [ { name = "flo-cloud", editable = "packages/flo_cloud" }, { name = "httpx", specifier = ">=0.28.1,<1.0.0" }, { name = "psycopg", extras = ["binary", "pool"], specifier = ">=3.2.3,<4.0.0" }, - { name = "psycopg2", specifier = ">=2.9.10,<3.0.0" }, + { name = "psycopg2-binary", specifier = ">=2.9.10,<3.0.0" }, { name = "python-dotenv", specifier = ">=1.1.0,<2.0.0" }, { name = "python-jose", extras = ["cryptography"], specifier = ">=3.3.0,<4.0.0" }, { name = "sqlalchemy", specifier = ">=2.0.40,<3.0.0" }, @@ -2004,7 +2004,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" }, { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" }, { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, @@ -2015,7 +2014,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, - { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, @@ -2026,7 +2024,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, @@ -2037,7 +2034,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" }, @@ -2372,7 +2368,7 @@ dependencies = [ { name = "numpy" }, { name = "opencv-python" }, { name = "pillow" }, - { name = "psycopg2" }, + { name = "psycopg2-binary" }, { name = "python-dotenv" }, { name = "python-multipart" }, { name = "torchvision" }, @@ -2390,7 +2386,7 @@ requires-dist = [ { name = "numpy", specifier = ">=1.26.4,<2.0.0" }, { name = "opencv-python", specifier = ">=4.11.0.86" }, { name = "pillow", specifier = ">=11.1.0,<12" }, - { name = "psycopg2", specifier = ">=2.9.10,<3.0.0" }, + { name = "psycopg2-binary", specifier = ">=2.9.10,<3.0.0" }, { name = "python-dotenv", specifier = ">=1.1.0,<2.0.0" }, { name = "python-multipart", specifier = "==0.0.9" }, { name = "torchvision", specifier = "==0.16.0" }, @@ -2454,7 +2450,7 @@ dependencies = [ { name = "google-cloud-bigquery" }, { name = "google-cloud-storage" }, { name = "httpx" }, - { name = "psycopg2" }, + { name = "psycopg2-binary" }, { name = "pyyaml" }, { name = "redshift-connector" }, ] @@ -2478,7 +2474,7 @@ requires-dist = [ { name = "google-cloud-bigquery", specifier = "==3.34.0" }, { name = "google-cloud-storage", specifier = "<3.0.0" }, { name = "httpx", specifier = ">=0.28.1,<1.0.0" }, - { name = "psycopg2", specifier = ">=2.9.10,<3.0.0" }, + { name = "psycopg2-binary", specifier = ">=2.9.10,<3.0.0" }, { name = "pyyaml", specifier = ">=6.0.3,<7" }, { name = "redshift-connector", specifier = ">=2.1.5,<3.0.0" }, ] @@ -4341,16 +4337,55 @@ wheels = [ ] [[package]] -name = "psycopg2" -version = "2.9.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/62/51/2007ea29e605957a17ac6357115d0c1a1b60c8c984951c19419b3474cdfd/psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11", size = 385672, upload-time = "2024-10-16T11:24:54.832Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/a2/c51ca3e667c34e7852157b665e3d49418e68182081060231d514dd823225/psycopg2-2.9.10-cp311-cp311-win32.whl", hash = "sha256:47c4f9875125344f4c2b870e41b6aad585901318068acd01de93f3677a6522c2", size = 1024538, upload-time = "2024-10-16T11:18:33.48Z" }, - { url = "https://files.pythonhosted.org/packages/33/39/5a9a229bb5414abeb86e33b8fc8143ab0aecce5a7f698a53e31367d30caa/psycopg2-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4", size = 1163736, upload-time = "2024-10-16T11:18:36.616Z" }, - { url = "https://files.pythonhosted.org/packages/3d/16/4623fad6076448df21c1a870c93a9774ad8a7b4dd1660223b59082dd8fec/psycopg2-2.9.10-cp312-cp312-win32.whl", hash = "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067", size = 1025113, upload-time = "2024-10-16T11:18:40.148Z" }, - { url = "https://files.pythonhosted.org/packages/66/de/baed128ae0fc07460d9399d82e631ea31a1f171c0c4ae18f9808ac6759e3/psycopg2-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e", size = 1163951, upload-time = "2024-10-16T11:18:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/ae/49/a6cfc94a9c483b1fa401fbcb23aca7892f60c7269c5ffa2ac408364f80dc/psycopg2-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2", size = 2569060, upload-time = "2025-01-04T20:09:15.28Z" }, +name = "psycopg2-binary" +version = "2.9.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620, upload-time = "2025-10-10T11:14:48.041Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/ae/8d8266f6dd183ab4d48b95b9674034e1b482a3f8619b33a0d86438694577/psycopg2_binary-2.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0e8480afd62362d0a6a27dd09e4ca2def6fa50ed3a4e7c09165266106b2ffa10", size = 3756452, upload-time = "2025-10-10T11:11:11.583Z" }, + { url = "https://files.pythonhosted.org/packages/4b/34/aa03d327739c1be70e09d01182619aca8ebab5970cd0cfa50dd8b9cec2ac/psycopg2_binary-2.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:763c93ef1df3da6d1a90f86ea7f3f806dc06b21c198fa87c3c25504abec9404a", size = 3863957, upload-time = "2025-10-10T11:11:16.932Z" }, + { url = "https://files.pythonhosted.org/packages/48/89/3fdb5902bdab8868bbedc1c6e6023a4e08112ceac5db97fc2012060e0c9a/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e164359396576a3cc701ba8af4751ae68a07235d7a380c631184a611220d9a4", size = 4410955, upload-time = "2025-10-10T11:11:21.21Z" }, + { url = "https://files.pythonhosted.org/packages/ce/24/e18339c407a13c72b336e0d9013fbbbde77b6fd13e853979019a1269519c/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d57c9c387660b8893093459738b6abddbb30a7eab058b77b0d0d1c7d521ddfd7", size = 4468007, upload-time = "2025-10-10T11:11:24.831Z" }, + { url = "https://files.pythonhosted.org/packages/91/7e/b8441e831a0f16c159b5381698f9f7f7ed54b77d57bc9c5f99144cc78232/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c226ef95eb2250974bf6fa7a842082b31f68385c4f3268370e3f3870e7859ee", size = 4165012, upload-time = "2025-10-10T11:11:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/0d/61/4aa89eeb6d751f05178a13da95516c036e27468c5d4d2509bb1e15341c81/psycopg2_binary-2.9.11-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a311f1edc9967723d3511ea7d2708e2c3592e3405677bf53d5c7246753591fbb", size = 3981881, upload-time = "2025-10-30T02:55:07.332Z" }, + { url = "https://files.pythonhosted.org/packages/76/a1/2f5841cae4c635a9459fe7aca8ed771336e9383b6429e05c01267b0774cf/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb415404821b6d1c47353ebe9c8645967a5235e6d88f914147e7fd411419e6f", size = 3650985, upload-time = "2025-10-10T11:11:34.975Z" }, + { url = "https://files.pythonhosted.org/packages/84/74/4defcac9d002bca5709951b975173c8c2fa968e1a95dc713f61b3a8d3b6a/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f07c9c4a5093258a03b28fab9b4f151aa376989e7f35f855088234e656ee6a94", size = 3296039, upload-time = "2025-10-10T11:11:40.432Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c2/782a3c64403d8ce35b5c50e1b684412cf94f171dc18111be8c976abd2de1/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:00ce1830d971f43b667abe4a56e42c1e2d594b32da4802e44a73bacacb25535f", size = 3043477, upload-time = "2025-10-30T02:55:11.182Z" }, + { url = "https://files.pythonhosted.org/packages/c8/31/36a1d8e702aa35c38fc117c2b8be3f182613faa25d794b8aeaab948d4c03/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cffe9d7697ae7456649617e8bb8d7a45afb71cd13f7ab22af3e5c61f04840908", size = 3345842, upload-time = "2025-10-10T11:11:45.366Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b4/a5375cda5b54cb95ee9b836930fea30ae5a8f14aa97da7821722323d979b/psycopg2_binary-2.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:304fd7b7f97eef30e91b8f7e720b3db75fee010b520e434ea35ed1ff22501d03", size = 2713894, upload-time = "2025-10-10T11:11:48.775Z" }, + { url = "https://files.pythonhosted.org/packages/d8/91/f870a02f51be4a65987b45a7de4c2e1897dd0d01051e2b559a38fa634e3e/psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4", size = 3756603, upload-time = "2025-10-10T11:11:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/27/fa/cae40e06849b6c9a95eb5c04d419942f00d9eaac8d81626107461e268821/psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc", size = 3864509, upload-time = "2025-10-10T11:11:56.452Z" }, + { url = "https://files.pythonhosted.org/packages/2d/75/364847b879eb630b3ac8293798e380e441a957c53657995053c5ec39a316/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a", size = 4411159, upload-time = "2025-10-10T11:12:00.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a0/567f7ea38b6e1c62aafd58375665a547c00c608a471620c0edc364733e13/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e", size = 4468234, upload-time = "2025-10-10T11:12:04.892Z" }, + { url = "https://files.pythonhosted.org/packages/30/da/4e42788fb811bbbfd7b7f045570c062f49e350e1d1f3df056c3fb5763353/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db", size = 4166236, upload-time = "2025-10-10T11:12:11.674Z" }, + { url = "https://files.pythonhosted.org/packages/3c/94/c1777c355bc560992af848d98216148be5f1be001af06e06fc49cbded578/psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757", size = 3983083, upload-time = "2025-10-30T02:55:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/bd/42/c9a21edf0e3daa7825ed04a4a8588686c6c14904344344a039556d78aa58/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3", size = 3652281, upload-time = "2025-10-10T11:12:17.713Z" }, + { url = "https://files.pythonhosted.org/packages/12/22/dedfbcfa97917982301496b6b5e5e6c5531d1f35dd2b488b08d1ebc52482/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a", size = 3298010, upload-time = "2025-10-10T11:12:22.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/ea/d3390e6696276078bd01b2ece417deac954dfdd552d2edc3d03204416c0c/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34", size = 3044641, upload-time = "2025-10-30T02:55:19.929Z" }, + { url = "https://files.pythonhosted.org/packages/12/9a/0402ded6cbd321da0c0ba7d34dc12b29b14f5764c2fc10750daa38e825fc/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d", size = 3347940, upload-time = "2025-10-10T11:12:26.529Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d2/99b55e85832ccde77b211738ff3925a5d73ad183c0b37bcbbe5a8ff04978/psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d", size = 2714147, upload-time = "2025-10-10T11:12:29.535Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572, upload-time = "2025-10-10T11:12:32.873Z" }, + { url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529, upload-time = "2025-10-10T11:12:36.791Z" }, + { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242, upload-time = "2025-10-10T11:12:42.388Z" }, + { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258, upload-time = "2025-10-10T11:12:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295, upload-time = "2025-10-10T11:12:52.525Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7d/c07374c501b45f3579a9eb761cbf2604ddef3d96ad48679112c2c5aa9c25/psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f", size = 3983133, upload-time = "2025-10-30T02:55:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383, upload-time = "2025-10-10T11:12:56.387Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168, upload-time = "2025-10-10T11:13:00.403Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/50c3facc66bded9ada5cbc0de867499a703dc6bca6be03070b4e3b65da6c/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60", size = 3044712, upload-time = "2025-10-30T02:55:27.975Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549, upload-time = "2025-10-10T11:13:03.971Z" }, + { url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215, upload-time = "2025-10-10T11:13:07.14Z" }, + { url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567, upload-time = "2025-10-10T11:13:11.885Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a9/9d55c614a891288f15ca4b5209b09f0f01e3124056924e17b81b9fa054cc/psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f", size = 3864755, upload-time = "2025-10-10T11:13:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646, upload-time = "2025-10-10T11:13:24.432Z" }, + { url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701, upload-time = "2025-10-10T11:13:29.266Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293, upload-time = "2025-10-10T11:13:33.336Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e0/f8cc36eadd1b716ab36bb290618a3292e009867e5c97ce4aba908cb99644/psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f", size = 3983184, upload-time = "2025-10-30T02:55:32.483Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650, upload-time = "2025-10-10T11:13:38.181Z" }, + { url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663, upload-time = "2025-10-10T11:13:44.878Z" }, + { url = "https://files.pythonhosted.org/packages/97/77/21b0ea2e1a73aa5fa9222b2a6b8ba325c43c3a8d54272839c991f2345656/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b", size = 3044737, upload-time = "2025-10-30T02:55:35.69Z" }, + { url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643, upload-time = "2025-10-10T11:13:53.499Z" }, + { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" }, ] [[package]]