From 651bfe8f8329acf106ac3b38fd24c981ec6dbffa Mon Sep 17 00:00:00 2001 From: dlbrittain Date: Wed, 11 Jun 2025 08:41:16 -0700 Subject: [PATCH 1/6] feat: add ref table drop down --- .../blueprints/upload/api.py | 95 ++++++++++++++++++- static/js/step3.js | 65 +++++++++++-- templates/upload/step3.html | 29 +++++- 3 files changed, 173 insertions(+), 16 deletions(-) diff --git a/materializationengine/blueprints/upload/api.py b/materializationengine/blueprints/upload/api.py index 1a0c8d76..1099cef8 100644 --- a/materializationengine/blueprints/upload/api.py +++ b/materializationengine/blueprints/upload/api.py @@ -48,7 +48,7 @@ get_job_status, process_and_upload, ) -from materializationengine.database import db_manager +from materializationengine.database import db_manager, dynamic_annotation_cache from materializationengine.info_client import get_datastack_info, get_datastacks from materializationengine.utils import get_config_param from materializationengine import __version__ @@ -290,7 +290,7 @@ def create_storage_service(): config = StorageConfig( allowed_origin=current_app.config.get("ALLOWED_ORIGIN"), ) - bucket_name = current_app.config.get("MATERIALIZATION_UPLOAD_BUCKET_PATH") + bucket_name = current_app.config.get("MATERIALIZATION_UPLOAD_BUCKET_NAME") return StorageService(bucket_name, logger=current_app.logger) @@ -300,7 +300,7 @@ def generate_presigned_url(datastack_name: str): data = request.json filename = data["filename"] content_type = data["contentType"] - bucket_name = current_app.config.get("MATERIALIZATION_UPLOAD_BUCKET_PATH") + bucket_name = current_app.config.get("MATERIALIZATION_UPLOAD_BUCKET_NAME") storage_client = storage.Client() bucket = storage_client.bucket(bucket_name) blob = bucket.blob(filename) @@ -352,6 +352,33 @@ def get_schema_types_endpoint(): return jsonify({"status": "error", "message": str(e)}), 500 +@upload_bp.route("/api/datastack//valid-annotation-tables", methods=["GET"]) +@auth_required +def get_valid_annotation_tables(datastack_name: str): + """Get list of valid annotation tables for a given datastack.""" + try: + datastack_info = get_datastack_info(datastack_name) + if not datastack_info: + return jsonify({"status": "error", "message": "Datastack not found"}), 404 + + aligned_volume_name = datastack_info["aligned_volume"]["name"] + + + db = dynamic_annotation_cache.get_db(aligned_volume_name) + + + table_names = db.database.get_valid_table_names() + + return jsonify({"status": "success", "table_names": table_names}) + + except Exception as e: + current_app.logger.error(f"Error getting valid annotation tables for {datastack_name}: {str(e)}") + return ( + jsonify({"status": "error", "message": f"Failed to get valid annotation tables: {str(e)}"}), + 500, + ) + + @upload_bp.route("/api/get-schema-model", methods=["GET"]) @auth_required def get_schema_model(): @@ -642,7 +669,7 @@ def start_csv_processing(): schema = UploadRequestSchema() file_metadata = schema.load(r) - bucket_name = current_app.config.get("MATERIALIZATION_UPLOAD_BUCKET_PATH") + bucket_name = current_app.config.get("MATERIALIZATION_UPLOAD_BUCKET_NAME") file_path = f"gs://{bucket_name}/{file_metadata.get('filename')}" @@ -819,6 +846,66 @@ def cancel_job(job_id): return jsonify({"status": "error", "message": str(e)}), 500 +@upload_bp.route("/api/process/dismiss/", methods=["DELETE"]) +@auth_required +def dismiss_job(job_id): + """Dismiss/delete a completed, failed, or cancelled job""" + try: + status = get_job_status(job_id) + if not status: + return jsonify({"status": "error", "message": "Job not found"}), 404 + + if not is_auth_disabled() and g.get("auth_user"): + user_id = str(g.auth_user["id"]) + job_user_id = status.get("user_id") + datastack_name = status.get("datastack_name") + + can_dismiss = False + if job_user_id == user_id: + can_dismiss = True + elif datastack_name and _has_datastack_permission( + g.auth_user, "admin", datastack_name + ): + can_dismiss = True + + if not can_dismiss: + return ( + jsonify( + {"status": "error", "message": "Forbidden to dismiss this job"} + ), + 403, + ) + + job_status = status.get("status", "").lower() + if job_status in ["processing", "pending", "preparing"]: + return ( + jsonify( + { + "status": "error", + "message": "Cannot dismiss active jobs. Cancel the job first." + } + ), + 400, + ) + + job_key = f"csv_processing:{job_id}" + deleted = REDIS_CLIENT.delete(job_key) + + if deleted: + current_app.logger.info(f"Job {job_id} dismissed by user {user_id if g.get('auth_user') else 'unknown'}") + return jsonify( + {"status": "success", "message": "Job dismissed successfully"} + ) + else: + return jsonify( + {"status": "error", "message": "Job not found or already dismissed"} + ), 404 + + except Exception as e: + current_app.logger.error(f"Error dismissing job {job_id}: {str(e)}", exc_info=True) + return jsonify({"status": "error", "message": str(e)}), 500 + + @upload_bp.route("/api/process/user-jobs", methods=["GET"]) @auth_required def get_user_jobs(): diff --git a/static/js/step3.js b/static/js/step3.js index 6448d2ed..d7d29692 100644 --- a/static/js/step3.js +++ b/static/js/step3.js @@ -7,31 +7,32 @@ document.addEventListener("alpine:init", () => { description: "", notice_text: "", reference_table: "", + referenceTableOptions: [], flat_segmentation_source: "", voxel_resolution_nm_x: 1, voxel_resolution_nm_y: 1, voxel_resolution_nm_z: 1, - write_permission: "PRIVATE", - read_permission: "PRIVATE", + write_permission: "PUBLIC", + read_permission: "PUBLIC", validationErrors: {}, isReferenceSchema: false, metadataSaved: false, }, - init() { - this.loadInitialState(); - this.checkIfReferenceSchema(); + async init() { + await this.loadInitialState(); + if (Object.keys(this.state).some((key) => this.state[key])) { this.validateForm(); } }, - loadInitialState() { + async loadInitialState() { const savedState = localStorage.getItem("metadataStore"); if (savedState) { const state = JSON.parse(savedState); Object.keys(state).forEach((key) => { - if (key !== "validationErrors") { + if (key !== "validationErrors" && key !== "referenceTableOptions") { this.state[key] = state[key]; } }); @@ -47,11 +48,13 @@ document.addEventListener("alpine:init", () => { } } } + await this.checkIfReferenceSchema(); }, saveState() { const stateToSave = { ...this.state }; delete stateToSave.validationErrors; + delete stateToSave.referenceTableOptions; localStorage.setItem("metadataStore", JSON.stringify(stateToSave)); }, @@ -66,7 +69,51 @@ document.addEventListener("alpine:init", () => { if (schemaModel && schemaModel.fields) { this.state.isReferenceSchema = "target_id" in schemaModel.fields; + } else { + this.state.isReferenceSchema = false; + } + } else { + this.state.isReferenceSchema = false; + } + + if (this.state.isReferenceSchema) { + await this.fetchReferenceTableOptions(); + } else { + this.state.referenceTableOptions = []; + this.state.reference_table = ""; + } + }, + + async fetchReferenceTableOptions() { + if (!this.state.datastack_name) { + console.warn("Datastack name not set, cannot fetch reference tables."); + this.state.referenceTableOptions = []; + return; + } + try { + const response = await fetch( + `/materialize/upload/api/datastack/${this.state.datastack_name}/valid-annotation-tables` + ); + if (!response.ok) { + const errorData = await response + .json() + .catch(() => ({ message: "Failed to fetch reference tables" })); + throw new Error( + errorData.message || "Failed to fetch reference tables" + ); + } + const data = await response.json(); + if (data.status === "success" && Array.isArray(data.table_names)) { + this.state.referenceTableOptions = data.table_names; + } else { + this.state.referenceTableOptions = []; + console.error("Error in fetching reference tables:", data.message); } + } catch (error) { + console.error("Error fetching reference table options:", error); + this.state.referenceTableOptions = []; + this.state.validationErrors.reference_table = + "Could not load reference tables."; } }, @@ -90,6 +137,10 @@ document.addEventListener("alpine:init", () => { "Reference table is required for this schema type"; } + if (this.state.isReferenceSchema && this.state.referenceTableOptions.length > 0 && !this.state.reference_table) { + errors.reference_table = "Please select a reference table."; + } + ["x", "y", "z"].forEach((dim) => { const value = this.state[`voxel_resolution_nm_${dim}`]; if (!value || value <= 0) { diff --git a/templates/upload/step3.html b/templates/upload/step3.html index 341698c5..a92f2fc9 100644 --- a/templates/upload/step3.html +++ b/templates/upload/step3.html @@ -44,12 +44,31 @@

Step 3: Annotation Table Metadata

- - -
+ + +
+ Loading reference tables or none available for the selected datastack. + Please select a datastack first.
+
+
+ + +
+ +
From 964144f0739190b1950c29d9d4414d0e7ed1a14b Mon Sep 17 00:00:00 2001 From: dlbrittain Date: Wed, 11 Jun 2025 08:42:11 -0700 Subject: [PATCH 2/6] fix: clearer ui text for pt position column mode --- templates/upload/step2.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/upload/step2.html b/templates/upload/step2.html index 091970ef..ef22f079 100644 --- a/templates/upload/step2.html +++ b/templates/upload/step2.html @@ -68,7 +68,7 @@
/>
From 230e6915c9bc7c708a5cdbe720aa7f31ddf0450c Mon Sep 17 00:00:00 2001 From: dlbrittain Date: Wed, 11 Jun 2025 08:43:40 -0700 Subject: [PATCH 3/6] fix: use bucket name not path for config --- materializationengine/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/materializationengine/app.py b/materializationengine/app.py index 3c096e83..e4451e54 100644 --- a/materializationengine/app.py +++ b/materializationengine/app.py @@ -115,7 +115,7 @@ def index(): # setup cors on upload bucket try: - bucket_name = app.config.get("MATERIALIZATION_UPLOAD_BUCKET_PATH") + bucket_name = app.config.get("MATERIALIZATION_UPLOAD_BUCKET_NAME") storage_service = StorageService(bucket_name) storage_service.configure_cors() except Exception as e: From 7a54f02ed6da52f474306ef9316b4f4eeb1a6a78 Mon Sep 17 00:00:00 2001 From: dlbrittain Date: Wed, 11 Jun 2025 08:46:12 -0700 Subject: [PATCH 4/6] fix: Missing or invalid 'data_chunk_bounds_list' --- materializationengine/workflows/chunking.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/materializationengine/workflows/chunking.py b/materializationengine/workflows/chunking.py index e5aef510..4f296c2d 100644 --- a/materializationengine/workflows/chunking.py +++ b/materializationengine/workflows/chunking.py @@ -249,7 +249,9 @@ def select_strategy(self): data_chunking_info = self._create_data_specific_chunks() if data_chunking_info: - self._chunk_generator, self.total_chunks = data_chunking_info + data_specific_bounds, chunk_count = data_chunking_info + self.data_chunk_bounds_list = data_specific_bounds + self.total_chunks = chunk_count self.strategy_name = "data_chunks" celery_logger.info( f"Using data-specific chunking with {self.total_chunks} chunks of size {self.actual_chunk_size}" From 4ad35b1e4cce7db316d217fe2df82f0b13110569 Mon Sep 17 00:00:00 2001 From: dlbrittain Date: Wed, 11 Jun 2025 08:49:07 -0700 Subject: [PATCH 5/6] fix: handle reference tables and tables with no needed segmentation columns --- .../blueprints/upload/tasks.py | 311 +++++++++++++----- materializationengine/index_manager.py | 6 +- 2 files changed, 226 insertions(+), 91 deletions(-) diff --git a/materializationengine/blueprints/upload/tasks.py b/materializationengine/blueprints/upload/tasks.py index 34f5c85c..b0091f1f 100644 --- a/materializationengine/blueprints/upload/tasks.py +++ b/materializationengine/blueprints/upload/tasks.py @@ -1,19 +1,26 @@ import json import os +import shlex import subprocess from datetime import datetime, timezone from typing import Any, Dict, List -import shlex import pandas as pd from celery import chain from celery.result import AsyncResult from celery.utils.log import get_task_logger +from dynamicannotationdb.key_utils import build_segmentation_table_name +from dynamicannotationdb.models import AnnoMetadata +from dynamicannotationdb.schema import DynamicSchemaClient from flask import current_app from redis import Redis from sqlalchemy import text -from dynamicannotationdb.key_utils import build_segmentation_table_name +from materializationengine.blueprints.upload.checkpoint_manager import ( + CHUNK_STATUS_COMPLETED, + CHUNK_STATUS_ERROR, + RedisCheckpointManager, +) from materializationengine.blueprints.upload.gcs_processor import GCSCsvProcessor from materializationengine.blueprints.upload.processor import SchemaProcessor from materializationengine.celery_init import celery @@ -26,15 +33,7 @@ create_segmentation_model, ) from materializationengine.workflows.spatial_lookup import run_spatial_lookup_workflow -from materializationengine.blueprints.upload.checkpoint_manager import ( - CHUNK_STATUS_COMPLETED, - CHUNK_STATUS_FAILED_PERMANENT, - CHUNK_STATUS_FAILED_RETRYABLE, - CHUNK_STATUS_PROCESSING, - CHUNK_STATUS_PROCESSING_SUBTASKS, - CHUNK_STATUS_ERROR, - RedisCheckpointManager, -) + celery_logger = get_task_logger(__name__) # Redis client for storing job status @@ -93,6 +92,10 @@ def process_and_upload( ignored_columns = file_metadata.get("ignored_columns") main_job_id = f"{datastack_name}_{table_name}_{materialization_time_stamp.strftime('%Y%m%d_%H%M%S')}" + needs_segmentation_table = DynamicSchemaClient.is_segmentation_table_required( + schema_type + ) + workflow = chain( process_csv.si( file_path=file_path, @@ -108,22 +111,33 @@ def process_and_upload( datastack_info=datastack_info, job_id=main_job_id, ), - run_spatial_lookup_workflow.si( - datastack_info=datastack_info, - table_name=table_name, - chunk_scale_factor=chunk_scale_factor, - supervoxel_batch_size=supervoxel_batch_size, - use_staging_database=True, - ), - monitor_spatial_workflow_completion.s( - datastack_info=datastack_info, - table_name_for_transfer=table_name, - materialization_time_stamp=str(materialization_time_stamp), - job_id_for_status=main_job_id, - ), - transfer_to_production.s( - transfer_segmentation=True, - ), + ) + + if needs_segmentation_table: + transfer_segmentation = True + + spatial_lookup_workflow = chain( + run_spatial_lookup_workflow.si( + datastack_info=datastack_info, + table_name=table_name, + chunk_scale_factor=chunk_scale_factor, + supervoxel_batch_size=supervoxel_batch_size, + use_staging_database=True, + ), + monitor_spatial_workflow_completion.s( + datastack_info=datastack_info, + table_name_for_transfer=table_name, + materialization_time_stamp=str(materialization_time_stamp), + job_id_for_status=main_job_id, + ), + ) + + workflow = workflow | spatial_lookup_workflow + else: + transfer_segmentation = False + + workflow = workflow | transfer_to_production.s( + transfer_segmentation=transfer_segmentation, ) result = workflow.apply_async() @@ -200,7 +214,7 @@ def process_csv( ignored_columns=ignored_columns, ) - bucket_name = current_app.config.get("MATERIALIZATION_UPLOAD_BUCKET_PATH") + bucket_name = current_app.config.get("MATERIALIZATION_UPLOAD_BUCKET_NAME") gcs_processor = GCSCsvProcessor(bucket_name, chunk_size=chunk_size) @@ -283,8 +297,6 @@ def upload_to_database( processed_rows_from_csv = process_result.get("processed_rows", 0) total_rows_from_csv = process_result.get("total_rows", "N/A") - datastack_name_from_info = datastack_info.get("datastack", "unknown_datastack") - staging_database_name = current_app.config.get("STAGING_DATABASE_NAME") try: table_name = file_metadata["metadata"]["table_name"] @@ -300,9 +312,7 @@ def upload_to_database( "flat_segmentation_source" ) if file_metadata["metadata"].get("is_reference_schema"): - target_table = file_metadata["metadata"].get( - "reference_table" - ) + target_table = file_metadata["metadata"].get("reference_table") table_metadata = {"reference_table": target_table} else: table_metadata = None @@ -351,6 +361,72 @@ def upload_to_database( ) db_client = dynamic_annotation_cache.get_db(staging_database) + # Handle foreign key dependencies by creating temporary reference tables if needed; TODO cleanup old temp tables + if table_metadata and table_metadata.get("reference_table"): + reference_table_name = table_metadata["reference_table"] + celery_logger.info( + f"Checking if reference table '{reference_table_name}' exists in staging database" + ) + + ref_table = db_client.database.get_table_metadata(reference_table_name) + if ref_table: + celery_logger.info( + f"Reference table '{reference_table_name}' exists in staging database" + ) + else: + celery_logger.info( + f"Reference table '{reference_table_name}' does not exist in staging database" + ) + celery_logger.info( + f"Creating temporary reference table '{reference_table_name}' to satisfy foreign key dependency" + ) + + try: + from sqlalchemy import Column, Integer, Table + + temp_table = Table( + reference_table_name, + db_client.database.base.metadata, + Column("id", Integer, primary_key=True), + extend_existing=True, + ) + + temp_table.create(db_client.database.engine, checkfirst=True) + + temp_reference_metadata = { + "description": f"Temporary reference table for staging database - references {reference_table_name}", + "user_id": user_id, + "reference_table": None, + "schema_type": "temporary_reference", + "table_name": reference_table_name, + "valid": True, + "created": datetime.now(timezone.utc), + "flat_segmentation_source": None, + "voxel_resolution_x": 1.0, + "voxel_resolution_y": 1.0, + "voxel_resolution_z": 1.0, + "read_permission": "PUBLIC", + "write_permission": "PRIVATE", + "last_modified": datetime.now(timezone.utc), + "notice_text": "Temporary table created for foreign key dependency in staging database. This table only contains ID column for reference purposes.", + } + + anno_metadata = AnnoMetadata(**temp_reference_metadata) + db_client.database.cached_session.add(anno_metadata) + db_client.database.commit_session() + + celery_logger.info( + f"Successfully created temporary reference table '{reference_table_name}' with metadata and registered in DynamicAnnotationDB metadata" + ) + + except Exception as ref_error: + celery_logger.error( + f"Failed to create temporary reference table '{reference_table_name}': {str(ref_error)}" + ) + celery_logger.warning( + "Continuing with main table creation - it may fail if foreign key constraint is strict" + ) + try: db_client.annotation.create_table( table_name=table_name, @@ -366,8 +442,12 @@ def upload_to_database( read_permission=read_permission, notice_text=notice_text, ) + celery_logger.info( + f"Successfully created main table '{table_name}' in staging database" + ) except Exception as e: - celery_logger.error(f"Error creating table: {str(e)}") + celery_logger.error(f"Error creating table '{table_name}': {str(e)}") + raise update_job_status( job_id_for_status, @@ -549,9 +629,19 @@ def upload_to_database( ) schema_model = db_client.schema.create_annotation_model(table_name, schema) + if file_metadata["metadata"].get("is_reference_schema"): + celery_logger.info( + f"Ignoring foreign keys for reference table {table_name}" + ) + skip_foreign_keys = True + else: + skip_foreign_keys = False anno_indices = index_cache.add_indices_sql_commands( - table_name, schema_model, db_client.database.engine + table_name, + schema_model, + db_client.database.engine, + skip_foreign_keys=skip_foreign_keys, ) for i, index_sql in enumerate(anno_indices): celery_logger.info(f"Adding index ({i+1}/{len(anno_indices)}): {index_sql}") @@ -577,11 +667,6 @@ def upload_to_database( }, ) - spatial_lookup_config = { - "table_name": table_name, - "datastack_name": datastack_name_from_info, - "database_name": staging_database_name, - } update_job_status( job_id_for_status, { @@ -616,6 +701,7 @@ def upload_to_database( ) raise + @celery.task( name="process:monitor_spatial_workflow_completion", bind=True, max_retries=None ) @@ -726,7 +812,8 @@ def monitor_spatial_workflow_completion( f"Not yet complete. Retrying in 60 seconds." ) raise self.retry(countdown=60) - + + @celery.task(name="process:transfer_to_production", bind=True, ack_late=True) def transfer_to_production( self, @@ -742,20 +829,27 @@ def transfer_to_production( datastack_info = monitor_result["datastack_info"] table_name_to_transfer = monitor_result["table_name"] materialization_time_stamp_str = monitor_result["materialization_time_stamp"] - spatial_workflow_status = monitor_result.get("spatial_workflow_final_status", "UNKNOWN") - + spatial_workflow_status = monitor_result.get( + "spatial_workflow_final_status", "UNKNOWN" + ) + try: - materialization_time_stamp_dt = datetime.fromisoformat(materialization_time_stamp_str) + materialization_time_stamp_dt = datetime.fromisoformat( + materialization_time_stamp_str + ) except ValueError: - materialization_time_stamp_dt = datetime.strptime(materialization_time_stamp_str, '%Y-%m-%d %H:%M:%S.%f') - + materialization_time_stamp_dt = datetime.strptime( + materialization_time_stamp_str, "%Y-%m-%d %H:%M:%S.%f" + ) celery_logger.info( f"Executing transfer_to_production for table: '{table_name_to_transfer}'. " f"Spatial workflow final status: '{spatial_workflow_status}'." ) - if "workflow_details" in monitor_result: - celery_logger.info(f"Spatial workflow details: {monitor_result['workflow_details']}") + if "workflow_details" in monitor_result: + celery_logger.info( + f"Spatial workflow details: {monitor_result['workflow_details']}" + ) staging_schema_name = get_config_param("STAGING_DATABASE_NAME") production_schema_name = datastack_info["aligned_volume"]["name"] @@ -766,16 +860,22 @@ def transfer_to_production( f"to production schema '{production_schema_name}'" ) - staging_db_client = dynamic_annotation_cache.get_db(staging_schema_name) - production_db_client = dynamic_annotation_cache.get_db(production_schema_name) + staging_db_client = dynamic_annotation_cache.get_db(staging_schema_name) + production_db_client = dynamic_annotation_cache.get_db(production_schema_name) - table_metadata_from_staging = staging_db_client.database.get_table_metadata(table_name_to_transfer) + table_metadata_from_staging = staging_db_client.database.get_table_metadata( + table_name_to_transfer + ) schema_type = table_metadata_from_staging.get("schema_type") if not schema_type: - raise ValueError(f"Could not determine schema type for table '{table_name_to_transfer}' in staging schema '{staging_schema_name}'") + raise ValueError( + f"Could not determine schema type for table '{table_name_to_transfer}' in staging schema '{staging_schema_name}'" + ) - needs_segmentation_table = staging_db_client.schema.is_segmentation_table_required(schema_type) + needs_segmentation_table = ( + staging_db_client.schema.is_segmentation_table_required(schema_type) + ) production_table_exists = False try: @@ -784,43 +884,56 @@ def transfer_to_production( celery_logger.info( f"Annotation table '{table_name_to_transfer}' already exists in production schema '{production_schema_name}'." ) - except Exception: + except Exception: production_table_exists = False celery_logger.info( f"Annotation table '{table_name_to_transfer}' does not exist in production schema '{production_schema_name}'. Will create." ) if not production_table_exists: - celery_logger.info(f"Creating annotation table '{table_name_to_transfer}' in production schema '{production_schema_name}'") + celery_logger.info( + f"Creating annotation table '{table_name_to_transfer}' in production schema '{production_schema_name}'" + ) production_db_client.annotation.create_table( table_name=table_name_to_transfer, schema_type=schema_type, description=table_metadata_from_staging.get("description", ""), user_id=table_metadata_from_staging.get("user_id", ""), - voxel_resolution_x=table_metadata_from_staging.get("voxel_resolution_x", 1.0), - voxel_resolution_y=table_metadata_from_staging.get("voxel_resolution_y", 1.0), - voxel_resolution_z=table_metadata_from_staging.get("voxel_resolution_z", 1.0), + voxel_resolution_x=table_metadata_from_staging.get( + "voxel_resolution_x", 1.0 + ), + voxel_resolution_y=table_metadata_from_staging.get( + "voxel_resolution_y", 1.0 + ), + voxel_resolution_z=table_metadata_from_staging.get( + "voxel_resolution_z", 1.0 + ), table_metadata=table_metadata_from_staging.get("table_metadata"), - flat_segmentation_source=table_metadata_from_staging.get("flat_segmentation_source"), - write_permission=table_metadata_from_staging.get("write_permission", "PRIVATE"), - read_permission=table_metadata_from_staging.get("read_permission", "PRIVATE"), + flat_segmentation_source=table_metadata_from_staging.get( + "flat_segmentation_source" + ), + write_permission=table_metadata_from_staging.get( + "write_permission", "PUBLIC" + ), + read_permission=table_metadata_from_staging.get( + "read_permission", "PUBLIC" + ), notice_text=table_metadata_from_staging.get("notice_text"), ) - production_engine = db_manager.get_engine(production_schema_name) - db_url_obj = production_engine.url - - - db_connection_info_for_cli = get_db_connection_info(db_url_obj) + db_url_obj = production_engine.url + db_connection_info_for_cli = get_db_connection_info(db_url_obj) - celery_logger.info(f"Transferring data for annotation table '{table_name_to_transfer}'") + celery_logger.info( + f"Transferring data for annotation table '{table_name_to_transfer}'" + ) annotation_rows_transferred = transfer_table_using_pg_dump( table_name=table_name_to_transfer, - source_db=staging_schema_name, - target_db=production_schema_name, - db_info=db_connection_info_for_cli, + source_db=staging_schema_name, + target_db=production_schema_name, + db_info=db_connection_info_for_cli, drop_indices=True, rebuild_indices=True, engine=production_engine, @@ -835,35 +948,50 @@ def transfer_to_production( segmentation_table_name = build_segmentation_table_name( table_name_to_transfer, pcg_table_name ) - celery_logger.info(f"Segmentation table processing for: '{segmentation_table_name}'") + celery_logger.info( + f"Segmentation table processing for: '{segmentation_table_name}'" + ) staging_segmentation_exists = False try: staging_db_client.database.get_table_metadata(segmentation_table_name) staging_segmentation_exists = True - except Exception: - celery_logger.info(f"Segmentation table '{segmentation_table_name}' not found in staging schema '{staging_schema_name}'. Skipping transfer for it.") + except Exception: + celery_logger.info( + f"Segmentation table '{segmentation_table_name}' not found in staging schema '{staging_schema_name}'. Skipping transfer for it." + ) if staging_segmentation_exists: - celery_logger.info(f"Preparing to transfer segmentation table '{segmentation_table_name}'") - + celery_logger.info( + f"Preparing to transfer segmentation table '{segmentation_table_name}'" + ) + mat_metadata_for_segmentation_table = { "annotation_table_name": table_name_to_transfer, "segmentation_table_name": segmentation_table_name, "schema_type": schema_type, - "database": production_schema_name, - "aligned_volume": production_schema_name, + "database": production_schema_name, + "aligned_volume": production_schema_name, "pcg_table_name": pcg_table_name, - "last_updated": materialization_time_stamp_dt, - "voxel_resolution_x": table_metadata_from_staging.get("voxel_resolution_x", 1.0), - "voxel_resolution_y": table_metadata_from_staging.get("voxel_resolution_y", 1.0), - "voxel_resolution_z": table_metadata_from_staging.get("voxel_resolution_z", 1.0), + "last_updated": materialization_time_stamp_dt, + "voxel_resolution_x": table_metadata_from_staging.get( + "voxel_resolution_x", 1.0 + ), + "voxel_resolution_y": table_metadata_from_staging.get( + "voxel_resolution_y", 1.0 + ), + "voxel_resolution_z": table_metadata_from_staging.get( + "voxel_resolution_z", 1.0 + ), } - - create_missing_segmentation_table(mat_metadata_for_segmentation_table, db_client=production_db_client) - - celery_logger.info(f"Transferring data for segmentation table '{segmentation_table_name}'") + create_missing_segmentation_table( + mat_metadata_for_segmentation_table, db_client=production_db_client + ) + + celery_logger.info( + f"Transferring data for segmentation table '{segmentation_table_name}'" + ) segmentation_rows_transferred = transfer_table_using_pg_dump( table_name=segmentation_table_name, source_db=staging_schema_name, @@ -872,7 +1000,9 @@ def transfer_to_production( drop_indices=True, rebuild_indices=True, engine=production_engine, - model_creator=lambda: create_segmentation_model(mat_metadata_for_segmentation_table), + model_creator=lambda: create_segmentation_model( + mat_metadata_for_segmentation_table + ), ) segmentation_transfer_results = { "name": segmentation_table_name, @@ -880,14 +1010,13 @@ def transfer_to_production( "rows_transferred": segmentation_rows_transferred, } else: - segmentation_transfer_results = { + segmentation_transfer_results = { "name": segmentation_table_name, "success": False, "message": "Not found in staging", "rows_transferred": 0, } - return { "status": "success", "message": f"Transfer completed for table '{table_name_to_transfer}'.", @@ -902,9 +1031,13 @@ def transfer_to_production( } except Exception as e: - celery_logger.error(f"Error during transfer_to_production for table '{monitor_result.get('table_name', 'UNKNOWN')}': {str(e)}", exc_info=True) + celery_logger.error( + f"Error during transfer_to_production for table '{monitor_result.get('table_name', 'UNKNOWN')}': {str(e)}", + exc_info=True, + ) raise + def get_db_connection_info(db_url): """Extract connection information from SQLAlchemy URL object.""" db_info = {} diff --git a/materializationengine/index_manager.py b/materializationengine/index_manager.py index b986320d..060d750a 100644 --- a/materializationengine/index_manager.py +++ b/materializationengine/index_manager.py @@ -180,13 +180,14 @@ def drop_table_indices(self, table_name: str, engine, drop_primary_key=True): raise (e) return True - def add_indices_sql_commands(self, table_name: str, model, engine): + def add_indices_sql_commands(self, table_name: str, model, engine, skip_foreign_keys=False): """Add missing indices by comparing reflected table and model indices. Will add missing indices from model to table. Args: table_name (str): target table to drop constraints and indices engine (SQLAlchemy Engine instance): supplied SQLAlchemy engine + skip_foreign_keys (bool): skip adding foreign keys Returns: str: list of indices added to table @@ -205,7 +206,8 @@ def add_indices_sql_commands(self, table_name: str, model, engine): command = f"CREATE INDEX IF NOT EXISTS {index_name} ON {table_name} ({column_name});" if index_type == "spatial_index": command = f"CREATE INDEX IF NOT EXISTS {index_name} ON {table_name} USING GIST ({column_name} gist_geometry_ops_nd);" - if index_type == "foreign_key": + + if index_type == "foreign_key" and not skip_foreign_keys: foreign_key_name = model_indices[index]["foreign_key_name"] foreign_key_table = model_indices[index]["foreign_key_table"] foreign_key_column = model_indices[index]["foreign_key_column"] From ca1ca6504b5ca9a269bbd5e6c7ff234c898a2183 Mon Sep 17 00:00:00 2001 From: dlbrittain Date: Sat, 30 Aug 2025 12:14:56 -0700 Subject: [PATCH 6/6] Merge latest master and bulk upload fixes --- .bumpversion.cfg | 2 +- .dockerignore | 3 +- .github/workflows/materialization_ci.yml | 39 +- .github/workflows/release.yml | 105 +- .github/workflows/update-dependency.yml | 126 + Dockerfile | 70 +- MANIFEST.ini | 2 +- build_docker.sh | 1 + cloudbuild.yaml | 2 + compile_reqs.sh | 1 - dev_requirements.txt | 1 - doc_requirements.txt | 4 - docs/conf.py | 2 +- materializationengine/__init__.py | 2 +- materializationengine/app.py | 19 +- .../blueprints/client/api.py | 2 +- .../blueprints/client/api2.py | 1578 +++++++- .../blueprints/client/common.py | 16 +- .../blueprints/client/datastack.py | 2 + .../blueprints/client/precomputed.py | 287 ++ .../blueprints/client/query.py | 5 +- .../blueprints/client/query_manager.py | 177 +- .../blueprints/client/utils.py | 1 - .../blueprints/materialize/api.py | 7 +- materializationengine/celery_worker.py | 27 +- materializationengine/config.py | 11 +- materializationengine/database.py | 56 +- materializationengine/request_db.py | 86 + materializationengine/shared_tasks.py | 43 +- materializationengine/utils.py | 13 +- materializationengine/views.py | 295 +- .../workflows/complete_workflow.py | 2 +- .../workflows/ingest_new_annotations.py | 2 +- .../workflows/periodic_database_removal.py | 24 +- .../workflows/periodic_materialization.py | 2 +- .../workflows/update_database_workflow.py | 4 +- pyproject.toml | 138 + requirements.in | 34 - requirements.txt | 560 --- run.py | 2 +- setup.py | 64 - test_requirements.txt | 4 - uv.lock | 3242 +++++++++++++++++ 43 files changed, 5992 insertions(+), 1071 deletions(-) create mode 100644 .github/workflows/update-dependency.yml create mode 100755 build_docker.sh delete mode 100755 compile_reqs.sh delete mode 100644 dev_requirements.txt delete mode 100644 doc_requirements.txt create mode 100644 materializationengine/blueprints/client/precomputed.py create mode 100644 materializationengine/request_db.py create mode 100644 pyproject.toml delete mode 100644 requirements.in delete mode 100644 requirements.txt delete mode 100644 setup.py delete mode 100644 test_requirements.txt create mode 100644 uv.lock diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 75b3d2ad..2a04aaa3 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 5.0.1 +current_version = 5.11.0 commit = True tag = True diff --git a/.dockerignore b/.dockerignore index 2271125a..5abb451b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,4 +6,5 @@ data src/ # ignore sentinel conf outputs *running.conf -*.git \ No newline at end of file +*.git +.venv/ \ No newline at end of file diff --git a/.github/workflows/materialization_ci.yml b/.github/workflows/materialization_ci.yml index 6a21b932..196db384 100644 --- a/.github/workflows/materialization_ci.yml +++ b/.github/workflows/materialization_ci.yml @@ -10,7 +10,8 @@ on: paths: - "materializationengine/**" - "tests/**" - - "requirements.txt" + - "uv.lock" + - "pyproject.toml" - ".github/workflows/**" pull_request: branches: master @@ -37,35 +38,17 @@ jobs: options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/test_requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - uses: conda-incubator/setup-miniconda@v3 + - name: Install uv + uses: astral-sh/setup-uv@v4 with: - auto-update-conda: true - auto-activate-base: true - python-version: ${{ matrix.python-version }} + version: "latest" + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} - name: Install dependencies - shell: bash -l {0} - run: | - pip install flake8 pytest - pip install -r requirements.txt - if [ -f test_requirements.txt ]; then pip install -r test_requirements.txt; fi - - name: Lint with flake8 - shell: bash -l {0} + run: uv sync --all-extras --dev + - name: Lint with ruff run: | - # stop the build if there are Python syntax errors or undefined names - # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + uv run ruff check . --output-format=github - name: Test with pytest - shell: bash -l {0} run: | - pytest \ No newline at end of file + uv run pytest \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b97fded0..827239a8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,6 @@ name: publish release on: - # run this workflow when manually triggered workflow_dispatch: inputs: part: @@ -21,6 +20,18 @@ on: type: boolean required: true default: false + workflow_call: + inputs: + part: + description: "Semver part to bump (major, minor, patch)" + type: string + required: false + default: "patch" + dry-run: + description: "Dry run" + type: boolean + required: false + default: false jobs: bump: @@ -49,16 +60,27 @@ jobs: python-version: "3.11" - name: Install bumpversion run: pip install bumpversion + - name: Validate version consistency + run: | + pyproject_version=$(grep '^version = ' pyproject.toml | cut -d'"' -f2) + init_version=$(grep '^__version__ = ' materializationengine/__init__.py | cut -d'"' -f2) + bumpversion_version=$(grep '^current_version = ' pyproject.toml | cut -d'"' -f2) + + echo "pyproject.toml version: $pyproject_version" + echo "__init__.py version: $init_version" + echo "bumpversion current_version: $bumpversion_version" + + if [ "$pyproject_version" != "$init_version" ] || [ "$pyproject_version" != "$bumpversion_version" ]; then + echo "❌ Version mismatch detected! All version files must be in sync before release." + exit 1 + fi + echo "✅ All versions are in sync" - name: Bump version with bumpversion run: | - bumpversion ${{ github.event.inputs.part }} - - name: Commit and push with tags - if: ${{ github.event.inputs.dry-run == 'false' }} - run: git push --follow-tags + bumpversion ${{ inputs.part || github.event.inputs.part }} - name: Get version id: get-version run: | - version="$(git describe --tags)" # remove the leading v from version version="${version:1}" @@ -71,3 +93,74 @@ jobs: echo "SHORT_VERSION=$short_version" >> $GITHUB_OUTPUT - name: Show short version run: echo ${{ steps.get-version.outputs.SHORT_VERSION }} + + - name: Commit and push with tags + if: ${{ (inputs.dry-run == false) || (github.event.inputs.dry-run == 'false') }} + run: | + git pull --rebase origin master + git push --follow-tags + + - name: Create GitHub Release + if: ${{ (inputs.dry-run == false) || (github.event.inputs.dry-run == 'false') }} + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.get-version.outputs.VERSION }} + name: Release v${{ steps.get-version.outputs.VERSION }} + body: | + ## MaterializationEngine v${{ steps.get-version.outputs.VERSION }} + + This release was automatically generated by the release workflow. + + ### Changes + - Version bumped from previous release + + See commit history for detailed changes. + draft: false + prerelease: false + + update-chart: + name: Update materializationengine Helm chart + runs-on: ubuntu-latest + needs: bump + if: ${{ (inputs.dry-run == false) || (github.event.inputs.dry-run == 'false') }} + permissions: + contents: read + id-token: write + steps: + - name: Checkout MaterializationEngine repo + uses: actions/checkout@v4 + + - name: Checkout cave-helm-charts repo + uses: actions/checkout@v4 + with: + repository: CAVEconnectome/cave-helm-charts + token: ${{ secrets.HELM_CHART_UPDATE_TOKEN }} + path: helm-charts + + - name: Update Chart.yaml appVersion + id: update-chart + run: | + cd helm-charts + chart_file="charts/materializationengine/Chart.yaml" + + # Update both appVersion and chart version to match application version + sed -i "s/^appVersion: .*/appVersion: \"${{ needs.bump.outputs.VERSION }}\"/" $chart_file + sed -i "s/^version: .*/version: ${{ needs.bump.outputs.VERSION }}/" $chart_file + + echo "Updated chart version to: ${{ needs.bump.outputs.VERSION }}" + echo "Updated app version to: ${{ needs.bump.outputs.VERSION }}" + echo "CHART_VERSION=${{ needs.bump.outputs.VERSION }}" >> $GITHUB_OUTPUT + + - name: Commit and push changes + run: | + cd helm-charts + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add charts/materializationengine/Chart.yaml + git commit -m "Update materializationengine to v${{ needs.bump.outputs.VERSION }} + + - Update appVersion to ${{ needs.bump.outputs.VERSION }} + - Update chart version to ${{ needs.bump.outputs.VERSION }} + + Auto-generated by MaterializationEngine release workflow" + git push diff --git a/.github/workflows/update-dependency.yml b/.github/workflows/update-dependency.yml new file mode 100644 index 00000000..3db82c11 --- /dev/null +++ b/.github/workflows/update-dependency.yml @@ -0,0 +1,126 @@ +name: Update Dependency + +on: + workflow_dispatch: + inputs: + dependency: + description: "Name of the dependency to update" + required: true + type: string + version: + description: "New version of the dependency" + required: true + type: string + bump_type: + description: "Type of version bump to perform after updating dependency" + type: choice + required: true + default: "minor" + options: ["major", "minor", "patch"] + +jobs: + update-dependency: + name: Update ${{ github.event.inputs.dependency }} dependency + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Update dependency in pyproject.toml + run: | + echo "Updating ${{ github.event.inputs.dependency }} to version ${{ github.event.inputs.version }}" + + # Update in pyproject.toml dependencies section + if [ -f "pyproject.toml" ]; then + # Handle both quoted and unquoted dependency specifications + sed -i "s/\"${{ github.event.inputs.dependency }}[^\"]*\"/\"${{ github.event.inputs.dependency }}>=${{ github.event.inputs.version }}\"/g" pyproject.toml + sed -i "s/'${{ github.event.inputs.dependency }}[^']*'/'${{ github.event.inputs.dependency }}>=${{ github.event.inputs.version }}'/g" pyproject.toml + echo "Updated pyproject.toml" + fi + + # Update in requirements.txt if it exists + if [ -f "requirements.txt" ]; then + sed -i "s/^${{ github.event.inputs.dependency }}==.*/${{ github.event.inputs.dependency }}==${{ github.event.inputs.version }}/" requirements.txt + echo "Updated requirements.txt" + fi + + # Update in requirements.in if it exists + if [ -f "requirements.in" ]; then + sed -i "s/^${{ github.event.inputs.dependency }}.*/${{ github.event.inputs.dependency }}>=${{ github.event.inputs.version }}/" requirements.in + echo "Updated requirements.in" + fi + + - name: Update uv.lock file + run: | + echo "Updating uv.lock file for ${{ github.event.inputs.dependency }}" + + # Try targeted update first (faster, less disruptive) + for i in {1..3}; do + echo "Attempt $i/3: Running uv sync --no-upgrade (targeted update)..." + if uv sync --no-upgrade; then + echo "✅ Targeted uv sync succeeded on attempt $i" + echo "Only ${{ github.event.inputs.dependency }} was updated, other dependencies unchanged" + echo "UPDATE_TYPE=targeted" >> $GITHUB_ENV + break + else + echo "❌ Targeted uv sync failed on attempt $i (likely dependency conflicts)" + if [ $i -eq 3 ]; then + echo "Targeted updates failed, falling back to full dependency resolution..." + + # Fallback to full sync (resolves conflicts but updates more deps) + for j in {1..3}; do + echo "Fallback attempt $j/3: Running full uv sync..." + if uv sync; then + echo "✅ Full uv sync succeeded on attempt $j" + echo "Dependency conflicts resolved, multiple packages may have been updated" + echo "UPDATE_TYPE=full" >> $GITHUB_ENV + break + else + echo "❌ Full uv sync failed on attempt $j" + if [ $j -lt 3 ]; then + echo "Waiting 30 seconds before retry..." + sleep 30 + else + echo "All sync attempts failed - dependency resolution impossible" + exit 1 + fi + fi + done + break + else + echo "Waiting 30 seconds before retry..." + sleep 30 + fi + fi + done + echo "Updated uv.lock" + + - name: Commit dependency update + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add -A + git diff --staged --quiet || git commit -m "Update ${{ github.event.inputs.dependency }} to v${{ github.event.inputs.version }} + + Auto-generated dependency update triggered by ${{ github.event.inputs.dependency }} release" + git push + + release: + name: Release with updated dependency + needs: update-dependency + uses: ./.github/workflows/release.yml + with: + part: ${{ github.event.inputs.bump_type }} + dry-run: false + secrets: inherit \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 582b8624..1e32046e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,42 +1,46 @@ -FROM debian:bookworm-slim AS gcloud_builder - -RUN apt-get update && \ - apt-get install -y --no-install-recommends curl ca-certificates gnupg bash python3-setuptools debianutils && \ - rm -rf /var/lib/apt/lists/* - -RUN curl -sSL https://sdk.cloud.google.com | bash -s -- --disable-prompts --install-dir=/opt/gcloud_install_temp - -FROM tiangolo/uwsgi-nginx-flask:python3.10 - +FROM tiangolo/uwsgi-nginx-flask:python3.12 AS builder +RUN apt-get update && apt-get install -y gcc +RUN pip install uv +# Enable bytecode compilation +ENV UV_COMPILE_BYTECODE=1 +ENV UV_LINK_MODE=copy +ENV UV_PYTHON_DOWNLOADS=0 +RUN mkdir -p /home/nginx/.cloudvolume/secrets \ + && chown -R nginx /home/nginx \ + && usermod -d /home/nginx -s /bin/bash nginx +# COPY requirements.txt /app/. +# RUN python -m pip install --upgrade pip +# RUN pip install -r requirements.txt +# Install gcloud SDK as root and set permissions +# Install gcloud SDK as root +USER nginx +RUN curl -sSL https://sdk.cloud.google.com | bash +ENV PATH /app/.venv/bin:/home/nginx/google-cloud-sdk/bin:/root/google-cloud-sdk/bin:$PATH USER root +# Install the project's dependencies using the lockfile and settings +WORKDIR /app -ENV UWSGI_INI=/app/uwsgi.ini -ENV PATH=/opt/google-cloud-sdk/bin:$PATH - -COPY --from=gcloud_builder /opt/gcloud_install_temp/google-cloud-sdk /opt/google-cloud-sdk +# Copy only the necessary files for dependency installation +COPY uv.lock pyproject.toml ./ +ENV UV_PROJECT_ENVIRONMENT="/usr/local/" +RUN --mount=type=cache,target=/root/.cache/uv \ + UV_VENV_ARGS="--system-site-packages" uv sync --frozen --no-install-project --no-default-groups -RUN apt-get update && \ - apt-get install -y --no-install-recommends postgresql-client && \ - rm -rf /var/lib/apt/lists/* && \ - mkdir -p /home/nginx/.cloudvolume/secrets && \ - chown -R nginx /home/nginx && \ - usermod -d /home/nginx -s /bin/bash nginx +# COPY . ./ +# RUN --mount=type=cache,target=/root/.cache/uv \ +# uv sync --frozen --no-default-groups -WORKDIR /app +ENV UWSGI_INI /app/uwsgi.ini +ENV PATH="/app/.venv/bin:$PATH" +ENV PYTHONNOUSERSITE=1 -COPY requirements.txt /app/ -RUN python -m pip install --no-cache-dir --upgrade pip && \ - pip install --no-cache-dir -r requirements.txt -COPY . /app COPY override/timeout.conf /etc/nginx/conf.d/timeout.conf -COPY gracefully_shutdown_celery.sh /home/nginx/ - - -RUN chmod +x /home/nginx/gracefully_shutdown_celery.sh && \ - mkdir -p /home/nginx/tmp/shutdown && \ - chmod +x /app/entrypoint.sh # Assuming entrypoint.sh is copied by 'COPY . /app' - +COPY gracefully_shutdown_celery.sh /home/nginx +RUN chmod +x /home/nginx/gracefully_shutdown_celery.sh +RUN mkdir -p /home/nginx/tmp/shutdown +RUN chmod +x /entrypoint.sh +WORKDIR /app -USER root +COPY . /app diff --git a/MANIFEST.ini b/MANIFEST.ini index 23fc8855..486efef5 100644 --- a/MANIFEST.ini +++ b/MANIFEST.ini @@ -1 +1 @@ -include LICENSE *requirements.txt README.md \ No newline at end of file +include LICENSE README.md pyproject.toml uv.lock \ No newline at end of file diff --git a/build_docker.sh b/build_docker.sh new file mode 100755 index 00000000..cbbe45be --- /dev/null +++ b/build_docker.sh @@ -0,0 +1 @@ +docker buildx build --platform linux/amd64 -t caveconnectome/materializationengine:$1 . \ No newline at end of file diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 68489aa2..dd246dc5 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -12,6 +12,8 @@ steps: - | docker build -t gcr.io/$PROJECT_ID/materializationengine:$TAG_NAME . timeout: 600s + env: + - "DOCKER_BUILDKIT=1" - name: "gcr.io/cloud-builders/docker" entrypoint: "bash" args: diff --git a/compile_reqs.sh b/compile_reqs.sh deleted file mode 100755 index 199fd69b..00000000 --- a/compile_reqs.sh +++ /dev/null @@ -1 +0,0 @@ -docker run -v ${PWD}:/app caveconnectome/materializationengine:py39 /bin/bash -c "pip install pip-tools && pip-compile -v -r requirements.in" \ No newline at end of file diff --git a/dev_requirements.txt b/dev_requirements.txt deleted file mode 100644 index 2c362e60..00000000 --- a/dev_requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pip-tools \ No newline at end of file diff --git a/doc_requirements.txt b/doc_requirements.txt deleted file mode 100644 index 01ace38c..00000000 --- a/doc_requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -sphinx -sphinxcontrib-napoleon -sphinxcontrib-apidoc -sphinx-rtd-theme \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index e4b52513..311eae7d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,7 +23,7 @@ author = "Derrick Brittain, Forrest Collman, Sven Dorkenwald" # The full version, including alpha/beta/rc tags -release = "5.0.1" +release = "5.11.0" diff --git a/materializationengine/__init__.py b/materializationengine/__init__.py index 2181bc3f..ddec482a 100644 --- a/materializationengine/__init__.py +++ b/materializationengine/__init__.py @@ -1 +1 @@ -__version__ = "5.0.1" \ No newline at end of file +__version__ = "5.11.0" \ No newline at end of file diff --git a/materializationengine/app.py b/materializationengine/app.py index e4451e54..5de3d51f 100644 --- a/materializationengine/app.py +++ b/materializationengine/app.py @@ -24,6 +24,7 @@ from materializationengine.views import views_bp from materializationengine.limiter import limiter from materializationengine.migrate import migrator +from materializationengine.request_db import init_request_db_cleanup db = SQLAlchemy(model_class=Base) @@ -34,6 +35,8 @@ def default(self, obj): return obj.tolist() if isinstance(obj, np.uint64): return int(obj) + if isinstance(obj, np.int64): + return int(obj) if isinstance(obj, (datetime, date)): return obj.isoformat() return json.JSONEncoder.default(self, obj) @@ -50,7 +53,7 @@ def create_app(config_name: str = None): template_folder="../templates", ) CORS(app, expose_headers=["WWW-Authenticate", "column_names"]) - logging.basicConfig(level=logging.INFO) + app.json_encoder = AEEncoder app.config["RESTX_JSON"] = {"cls": AEEncoder} @@ -59,8 +62,18 @@ def create_app(config_name: str = None): app.config.from_object(config[config_name]) else: app = configure_app(app) - - + logging.basicConfig(level=app.config['LOGGING_LEVEL']) + + # Suppress noisy debug messages from fsevents + logging.getLogger('fsevents').setLevel(app.config['LOGGING_LEVEL']) + logging.getLogger('urllib3').setLevel(app.config['LOGGING_LEVEL']) + logging.getLogger('google').setLevel(app.config['LOGGING_LEVEL']) + logging.getLogger('materializationengine').setLevel(app.config['LOGGING_LEVEL']) + logging.getLogger('root').setLevel(app.config['LOGGING_LEVEL']) + logging.getLogger('python_jsonschema_objects').setLevel(app.config['LOGGING_LEVEL']) + + # Initialize request-scoped database session cleanup + init_request_db_cleanup(app) # register blueprints apibp = Blueprint("api", __name__, url_prefix="/materialize") diff --git a/materializationengine/blueprints/client/api.py b/materializationengine/blueprints/client/api.py index b1853b78..61383649 100644 --- a/materializationengine/blueprints/client/api.py +++ b/materializationengine/blueprints/client/api.py @@ -30,7 +30,7 @@ from materializationengine.blueprints.client.datastack import validate_datastack -__version__ = "5.0.1" +__version__ = "5.11.0" authorizations = { diff --git a/materializationengine/blueprints/client/api2.py b/materializationengine/blueprints/client/api2.py index bf039c8d..556f1990 100644 --- a/materializationengine/blueprints/client/api2.py +++ b/materializationengine/blueprints/client/api2.py @@ -1,5 +1,9 @@ import datetime import json +import pytz +from dynamicannotationdb.models import AnalysisTable, AnalysisVersion + +from cachetools import TTLCache, cached, LRUCache from functools import wraps from typing import List @@ -34,6 +38,21 @@ from materializationengine.blueprints.client.common import ( unhandled_exception as common_unhandled_exception, ) +from materializationengine.request_db import request_db_session +import pandas as pd +import numpy as np +from marshmallow import fields as mm_fields +from emannotationschemas.schemas.base import PostGISField +import datetime +from typing import List +import werkzeug +from sqlalchemy.sql.sqltypes import String, Integer, Float, DateTime, Boolean, Numeric +import io +from geoalchemy2.types import Geometry +import nglui +from neuroglancer import viewer_state +import cloudvolume + from materializationengine.blueprints.client.datastack import validate_datastack from materializationengine.blueprints.client.new_query import ( remap_query, @@ -65,9 +84,12 @@ from materializationengine.models import MaterializedMetadata from materializationengine.schemas import AnalysisTableSchema, AnalysisVersionSchema from materializationengine.utils import check_read_permission +from materializationengine.blueprints.client.utils import update_notice_text_warnings +from materializationengine.blueprints.client.utils import after_request +from materializationengine.blueprints.client.precomputed import AnnotationWriter -__version__ = "5.0.1" +__version__ = "5.11.0" authorizations = { @@ -350,33 +372,109 @@ def execute_materialized_query( .filter(MaterializedMetadata.table_name == user_data["table"]) .scalar() ) - if random_sample: - if random_sample >= mat_row_count: + # Validate random_sample to prevent TABLESAMPLE errors + if random_sample is not None: + if not np.isfinite(random_sample) or random_sample <= 0: + print(f"WARNING: Invalid random_sample: {random_sample}, setting to None") + random_sample = None + elif mat_row_count <= 0: + print(f"WARNING: Invalid mat_row_count: {mat_row_count}, setting random_sample to None") + random_sample = None + elif random_sample >= mat_row_count: random_sample = None else: - random_sample = (100.0 * random_sample) / mat_row_count + percentage = (100.0 * random_sample) / mat_row_count + if not np.isfinite(percentage) or percentage <= 0: + print(f"WARNING: Invalid percentage calculation: {percentage}, setting random_sample to None") + random_sample = None + elif percentage > 100.0: + print(f"WARNING: Percentage > 100%: {percentage}, setting random_sample to None") + random_sample = None + else: + random_sample = percentage if mat_row_count: + # Decide between TABLESAMPLE and hash-based sampling based on sample size + hash_config = user_data.get("hash_sampling_config") + use_hash_sampling = False + use_random_sample = random_sample + + if hash_config and hash_config.get("enabled"): + # Use QUERY_LIMIT_SIZE from Flask config instead of parameter + max_points = current_app.config.get("PRECOMPUTED_OVERVIEW_MAX_SIZE", 50000) + + # Get configurable threshold for switching from TABLESAMPLE to hash sampling + hash_sampling_threshold = current_app.config.get("HASH_SAMPLING_THRESHOLD_PERCENT", 5.0) + volume_fraction = hash_config.get("volume_fraction", 1.0) + + # Validate volume_fraction to prevent invalid calculations + if not np.isfinite(volume_fraction) or volume_fraction <= 0: + print(f"WARNING: Invalid volume_fraction in hash_config: {volume_fraction}, using 1.0") + volume_fraction = 1.0 + elif volume_fraction > 1.0: + print(f"WARNING: volume_fraction > 1.0 in hash_config: {volume_fraction}, capping at 1.0") + volume_fraction = 1.0 + + # Calculate what percentage of the table we need to sample + if mat_row_count > 0 and volume_fraction > 0: + sample_percentage = (max_points * 100.0) / (mat_row_count * volume_fraction) + else: + print(f"WARNING: Invalid values for percentage calculation: mat_row_count={mat_row_count}, volume_fraction={volume_fraction}") + sample_percentage = 100.0 # Fallback to no sampling + + if sample_percentage >= 100.0: # Table is small enough - show all points + # No sampling needed, table has fewer rows than QUERY_LIMIT_SIZE + use_hash_sampling = False + use_random_sample = None + elif sample_percentage < hash_sampling_threshold: # Less than threshold - use TABLESAMPLE + # Calculate percentage needed (with some buffer to account for randomness) + use_random_sample = sample_percentage + # Validate that use_random_sample is valid for TABLESAMPLE + if not np.isfinite(use_random_sample) or use_random_sample <= 0: + print(f"WARNING: Invalid use_random_sample: {use_random_sample}, switching to hash sampling") + use_hash_sampling = True + use_random_sample = None + elif use_random_sample > 100.0: + print(f"WARNING: use_random_sample > 100%: {use_random_sample}, capping at 100%") + use_random_sample = 100.0 + use_hash_sampling = False + else: + use_hash_sampling = False + else: # Threshold to 100% of table - use hash-based sampling + use_hash_sampling = True + use_random_sample = None # Don't use TABLESAMPLE when using hash sampling + # setup a query manager qm = QueryManager( mat_db_name, segmentation_source=pcg_table_name, meta_db_name=aligned_volume, split_mode=split_mode, - random_sample=random_sample, + random_sample=use_random_sample, ) qm.configure_query(user_data) qm.apply_filter({user_data["table"]: {"valid": True}}, qm.apply_equal_filter) + + # Apply hash-based sampling if determined above + if use_hash_sampling: + qm.add_hash_spatial_sampling( + table_name=hash_config["table_name"], + spatial_column=hash_config["spatial_column"], + max_points=max_points, # Use the max_points from QUERY_LIMIT_SIZE + total_row_count=mat_row_count + ) + # return the result df, column_names = qm.execute_query( desired_resolution=user_data["desired_resolution"] ) df, warnings = update_rootids(df, user_data["timestamp"], query_map, cg_client) - if len(df) >= user_data["limit"]: - warnings.append( - f"result has {len(df)} entries, which is equal or more \ - than limit of {user_data['limit']} there may be more results which are not shown" - ) + if "limit" in user_data: + if len(df) >= user_data["limit"]: + warnings.append( + f"result has {len(df)} entries, which is equal or more \ + than limit of {user_data['limit']} there may be more results which are not shown" + ) return df, column_names, warnings else: return None, {}, [] @@ -432,11 +530,12 @@ def execute_production_query( df, warnings = update_rootids( df, user_timestamp, {}, cg_client, allow_missing_lookups ) - if len(df) >= user_data["limit"]: - warnings.append( - f"result has {len(df)} entries, which is equal or more \ -than limit of {user_data['limit']} there may be more results which are not shown" - ) + if "limit" in user_data: + if len(df) >= user_data["limit"]: + warnings.append( + f"result has {len(df)} entries, which is equal or more \ + than limit of {user_data['limit']} there may be more results which are not shown" + ) return df, column_names, warnings @@ -838,21 +937,21 @@ def get( return [], 404 - db = dynamic_annotation_cache.get_db(aligned_volume_name) - for table in analysis_tables: - - table_name = table["table_name"] - ann_md = db.database.get_table_metadata(table_name) - # the get_table_metadata function joins on the segmentationmetadata which - # has the segmentation_table in the table_name and the annotation table name in the annotation_table - # field. So when we update here, we overwrite the table_name with the segmentation table name, - # which was not the intent of the API. - ann_table = ann_md.pop("annotation_table", None) - if ann_table: - ann_md["table_name"] = ann_table - ann_md.pop("id") - ann_md.pop("deleted") - table.update(ann_md) + with request_db_session(aligned_volume_name) as db: + for table in analysis_tables: + + table_name = table["table_name"] + ann_md = db.database.get_table_metadata(table_name) + # the get_table_metadata function joins on the segmentationmetadata which + # has the segmentation_table in the table_name and the annotation table name in the annotation_table + # field. So when we update here, we overwrite the table_name with the segmentation table name, + # which was not the intent of the API. + ann_table = ann_md.pop("annotation_table", None) + if ann_table: + ann_md["table_name"] = ann_table + ann_md.pop("id") + ann_md.pop("deleted") + table.update(ann_md) return analysis_tables, 200 @@ -899,23 +998,23 @@ def get( - db = dynamic_annotation_cache.get_db(aligned_volume_name) - ann_md = db.database.get_table_metadata(table_name) - if ann_md is None: - return ( - f"No metadata found for table named {table_name} in version {version}", - 404, - ) - # the get_table_metadata function joins on the segmentationmetadata which - # has the segmentation_table in the table_name and the annotation table name in the annotation_table - # field. So when we update here, we overwrite the table_name with the segmentation table name, - # which was not the intent of the API. - ann_table = ann_md.pop("annotation_table", None) - if ann_table: - ann_md["table_name"] = ann_table - ann_md.pop("id") - ann_md.pop("deleted") - analysis_table.update(ann_md) + with request_db_session(aligned_volume_name) as db: + ann_md = db.database.get_table_metadata(table_name) + if ann_md is None: + return ( + f"No metadata found for table named {table_name} in version {version}", + 404, + ) + # the get_table_metadata function joins on the segmentationmetadata which + # has the segmentation_table in the table_name and the annotation table name in the annotation_table + # field. So when we update here, we overwrite the table_name with the segmentation table name, + # which was not the intent of the API. + ann_table = ann_md.pop("annotation_table", None) + if ann_table: + ann_md["table_name"] = ann_table + ann_md.pop("id") + ann_md.pop("deleted") + analysis_table.update(ann_md) return analysis_table, 200 @@ -1035,13 +1134,11 @@ def process_fields(df, fields, column_names, tags, bool_tags, numerical): continue # check that this column is not all nulls tags.append(col) - print(f"tag col: {col}") elif isinstance(field, mm_fields.Boolean): if df[col].isnull().all(): continue df[col] = df[col].astype(bool) bool_tags.append(col) - print(f"bool tag col: {col}") elif isinstance(field, PostGISField): # if all the values are NaNs skip this column if df[col + "_x"].isnull().all(): @@ -1049,12 +1146,10 @@ def process_fields(df, fields, column_names, tags, bool_tags, numerical): numerical.append(col + "_x") numerical.append(col + "_y") numerical.append(col + "_z") - print(f"numerical cols: {col}_(x,y,z)") elif isinstance(field, mm_fields.Number): if df[col].isnull().all(): continue numerical.append(col) - print(f"numerical col: {col}") def process_view_columns(df, model, column_names, tags, bool_tags, numerical): @@ -1074,13 +1169,11 @@ def process_view_columns(df, model, column_names, tags, bool_tags, numerical): continue # check that this column is not all nulls tags.append(col) - print(f"tag col: {col}") elif isinstance(table_column.type, Boolean): if df[col].isnull().all(): continue df[col] = df[col].astype(bool) bool_tags.append(col) - print(f"bool tag col: {col}") elif isinstance(table_column.type, PostGISField): # if all the values are NaNs skip this column if df[col + "_x"].isnull().all(): @@ -1088,58 +1181,59 @@ def process_view_columns(df, model, column_names, tags, bool_tags, numerical): numerical.append(col + "_x") numerical.append(col + "_y") numerical.append(col + "_z") - print(f"numerical cols: {col}_(x,y,z)") elif isinstance(table_column.type, (Numeric, Integer, Float)): if df[col].isnull().all(): continue numerical.append(col) - print(f"numerical col: {col}") -def preprocess_dataframe(df, table_name, aligned_volume_name, column_names): - db = dynamic_annotation_cache.get_db(aligned_volume_name) - # check if this is a reference table - table_metadata = db.database.get_table_metadata(table_name) - schema = db.schema.get_flattened_schema(table_metadata["schema_type"]) - fields = schema._declared_fields - if table_metadata["reference_table"]: - ref_table = table_metadata["reference_table"] - ref_table_metadata = db.database.get_table_metadata(ref_table) - ref_schema = db.schema.get_flattened_schema(ref_table_metadata["schema_type"]) - ref_fields = ref_schema._declared_fields +def preprocess_dataframe(df, table_name, aligned_volume_name, column_names): + with request_db_session(aligned_volume_name) as db: + # check if this is a reference table + table_metadata = db.database.get_table_metadata(table_name) + schema = db.schema.get_flattened_schema(table_metadata["schema_type"]) + fields = schema._declared_fields - # find the first column that ends with _root_id using next - try: - root_id_col = next( - (col for col in df.columns if col.endswith("_root_id")), None - ) - except StopIteration: - raise ValueError("No root_id column found in dataframe") + ref_fields = None + ref_table = None + if table_metadata["reference_table"]: + ref_table = table_metadata["reference_table"] + ref_table_metadata = db.database.get_table_metadata(ref_table) + ref_schema = db.schema.get_flattened_schema(ref_table_metadata["schema_type"]) + ref_fields = ref_schema._declared_fields + + # find the first column that ends with _root_id using next + try: + root_id_col = next( + (col for col in df.columns if col.endswith("_root_id")), None + ) + except StopIteration: + raise ValueError("No root_id column found in dataframe") - # pick only the first row with each root_id - # df = df.drop_duplicates(subset=[root_id_col]) - # drop any row with root_id =0 - df = df[df[root_id_col] != 0] + # pick only the first row with each root_id + # df = df.drop_duplicates(subset=[root_id_col]) + # drop any row with root_id =0 + df = df[df[root_id_col] != 0] - # iterate through the columns and put them into - # categories of 'tags' for strings, 'numerical' for numbers + # iterate through the columns and put them into + # categories of 'tags' for strings, 'numerical' for numbers - tags = [] - numerical = [] - bool_tags = [] + tags = [] + numerical = [] + bool_tags = [] - process_fields(df, fields, column_names[table_name], tags, bool_tags, numerical) + process_fields(df, fields, column_names[table_name], tags, bool_tags, numerical) - if table_metadata["reference_table"]: - process_fields( - df, - ref_fields, - column_names[ref_table], - tags, - bool_tags, - numerical, - ) + if table_metadata["reference_table"]: + process_fields( + df, + ref_fields, + column_names[ref_table], + tags, + bool_tags, + numerical, + ) # Look across the tag columns and make sure that there are no # duplicate string values across distinct columns unique_vals = {} @@ -1166,31 +1260,31 @@ def preprocess_dataframe(df, table_name, aligned_volume_name, column_names): def preprocess_view_dataframe(df, view_name, db_name, column_names): - db = dynamic_annotation_cache.get_db(db_name) - # check if this is a reference table - view_table = db.database.get_view_table(view_name) + with request_db_session(db_name) as db: + # check if this is a reference table + view_table = db.database.get_view_table(view_name) - # find the first column that ends with _root_id using next - try: - root_id_col = next( - (col for col in df.columns if col.endswith("_root_id")), None - ) - except StopIteration: - raise ValueError("No root_id column found in dataframe") + # find the first column that ends with _root_id using next + try: + root_id_col = next( + (col for col in df.columns if col.endswith("_root_id")), None + ) + except StopIteration: + raise ValueError("No root_id column found in dataframe") - # pick only the first row with each root_id - # df = df.drop_duplicates(subset=[root_id_col]) - # drop any row with root_id =0 - df = df[df[root_id_col] != 0] + # pick only the first row with each root_id + # df = df.drop_duplicates(subset=[root_id_col]) + # drop any row with root_id =0 + df = df[df[root_id_col] != 0] - # iterate through the columns and put them into - # categories of 'tags' for strings, 'numerical' for numbers + # iterate through the columns and put them into + # categories of 'tags' for strings, 'numerical' for numbers - tags = [] - numerical = [] - bool_tags = [] + tags = [] + numerical = [] + bool_tags = [] - process_view_columns(df, view_table, column_names, tags, bool_tags, numerical) + process_view_columns(df, view_table, column_names, tags, bool_tags, numerical) # Look across the tag columns and make sure that there are no # duplicate string values across distinct columns @@ -1272,9 +1366,9 @@ def get( if mat_row_count > current_app.config["QUERY_LIMIT_SIZE"]: return "Table too large to return info", 400 else: - db = dynamic_annotation_cache.get_db(aligned_volume_name) - # check if this is a reference table - table_metadata = db.database.get_table_metadata(table_name) + with request_db_session(aligned_volume_name) as db: + # check if this is a reference table + table_metadata = db.database.get_table_metadata(table_name) if table_metadata["reference_table"]: ref_table = table_metadata["reference_table"] @@ -1342,6 +1436,7 @@ def get( self, datastack_name: str, table_name: str, + version: int = 0, target_datastack: str = None, target_version: int = None, ): @@ -1357,9 +1452,9 @@ def get( aligned_volume_name, pcg_table_name = get_relevant_datastack_info( datastack_name ) - db = dynamic_annotation_cache.get_db(aligned_volume_name) - # check if this is a reference table - table_metadata = db.database.get_table_metadata(table_name) + with request_db_session(aligned_volume_name) as db: + # check if this is a reference table + table_metadata = db.database.get_table_metadata(table_name) user_data = { "table": table_name, @@ -1371,7 +1466,7 @@ def get( if table_metadata["reference_table"]: ref_table = table_metadata["reference_table"] user_data["suffixes"][ref_table] = "_ref" - user_data["joins"] = [[table_name, "target_id", ref_table, "id"]] + user_data["join_tables"] = [[table_name, "target_id", ref_table, "id"]] return_vals = assemble_live_query_dataframe( user_data, datastack_name=datastack_name, args={} @@ -1528,8 +1623,8 @@ def get(self, datastack_name: str, table_name: str): aligned_volume_name, pcg_table_name = get_relevant_datastack_info( datastack_name ) - db = dynamic_annotation_cache.get_db(aligned_volume_name) - unique_values = db.database.get_unique_string_values(table_name) + with request_db_session(aligned_volume_name) as db: + unique_values = db.database.get_unique_string_values(table_name) return unique_values, 200 @@ -1541,8 +1636,8 @@ def assemble_live_query_dataframe(user_data, datastack_name, args): past_ver, future_ver, aligned_vol = get_closest_versions( datastack_name, user_data["timestamp"] ) - db = dynamic_annotation_cache.get_db(aligned_vol) - check_read_permission(db, user_data["table"]) + with request_db_session(aligned_vol) as db: + check_read_permission(db, user_data["table"]) allow_invalid_root_ids = args.get("allow_invalid_root_ids", False) # TODO add table owner warnings # if has_joins: @@ -1562,7 +1657,6 @@ def assemble_live_query_dataframe(user_data, datastack_name, args): chosen_version = past_ver else: chosen_version = future_ver - print(chosen_version) loc_cv_time_stamp = chosen_version["time_stamp"] loc_cv_parent_version_id = chosen_version.get("parent_version") @@ -1604,15 +1698,15 @@ def assemble_live_query_dataframe(user_data, datastack_name, args): cg_client = chunkedgraph_cache.get_client(pcg_table_name_for_mat_query) meta_db_aligned_volume, _ = get_relevant_datastack_info(datastack_name) - meta_db = dynamic_annotation_cache.get_db(meta_db_aligned_volume) - md = meta_db.database.get_table_metadata(user_data["table"]) - if not user_data.get("desired_resolution", None): - des_res = [ - md["voxel_resolution_x"], - md["voxel_resolution_y"], - md["voxel_resolution_z"], - ] - user_data["desired_resolution"] = des_res + with request_db_session(meta_db_aligned_volume) as meta_db: + md = meta_db.database.get_table_metadata(user_data["table"]) + if not user_data.get("desired_resolution", None): + des_res = [ + md["voxel_resolution_x"], + md["voxel_resolution_y"], + md["voxel_resolution_z"], + ] + user_data["desired_resolution"] = des_res modified_user_data, query_map, remap_warnings = remap_query( user_data, @@ -1677,6 +1771,7 @@ def __init__(self, naive_timestamp): @client_bp.route("/datastack//query") class LiveTableQuery(Resource): method_decorators = [ + validate_datastack, limit_by_category("query"), auth_requires_permission("view", table_arg="datastack_name"), reset_auth, @@ -1684,12 +1779,14 @@ class LiveTableQuery(Resource): @client_bp.doc("v2_query", security="apikey") @accepts("V2QuerySchema", schema=V2QuerySchema, api=client_bp) - def post(self, datastack_name: str): + def post(self, datastack_name: str, + version: int = 0, + target_version: int = None, + target_datastack: str = None): """endpoint for doing a query with filters Args: datastack_name (str): datastack name - table_name (str): table names Payload: All values are optional. Limit has an upper bound set by the server. @@ -1751,6 +1848,7 @@ def post(self, datastack_name: str): "filter_regex_dict":{ "table_name":{ "column_name": "regex" + } } } Returns: @@ -1772,6 +1870,1181 @@ def post(self, datastack_name: str): ) +def find_position_prefixes(df): + """ + Find common prefixes in a DataFrame for which there are x, y, z components. + + Args: + df (pd.DataFrame): The DataFrame containing the columns. + + Returns: + set: A set of prefixes that have x, y, and z components. + """ + # Dictionary to track components for each prefix + prefix_map = {} + + for col in df.columns: + if col.endswith("_x") or col.endswith("_y") or col.endswith("_z"): + # Extract the prefix by removing the suffix + prefix = col.rsplit("_", 1)[0] + if not ( + prefix in ["ctr_pt_position", "bb_start_position", "bb_end_position"] + ): + if prefix not in prefix_map: + prefix_map[prefix] = set() + # Add the component (x, y, or z) to the prefix + prefix_map[prefix].add(col.split("_")[-1]) + + # Find prefixes that have all three components (x, y, z) + position_prefixes = { + prefix + for prefix, components in prefix_map.items() + if {"x", "y", "z"}.issubset(components) + } + columns = [] + # get the set of columns that are covered by the prefixes + for prefix in position_prefixes: + for suffix in ["_x", "_y", "_z"]: + col = f"{prefix}{suffix}" + if col in df.columns: + columns.append(col) + return position_prefixes, columns + + +@cached(LRUCache(maxsize=256)) +def get_precomputed_properties_and_relationships(datastack_name, table_name): + """Get precomputed relationships from the database. + + Args: + db: The database connection. + table_name (str): The name of the table. + """ + + aligned_volume_name, pcg_table_name = get_relevant_datastack_info(datastack_name) + + past_version, _, _ = get_closest_versions( + datastack_name, datetime.datetime.now(tz=pytz.utc) + ) + mat_db_name = f"{datastack_name}__mat{past_version['version']}" + with db_manager.session_scope(mat_db_name) as session: + mat_row_count = ( + session.query(MaterializedMetadata.row_count) + .filter(MaterializedMetadata.table_name == table_name) + .scalar() + ) + with request_db_session(aligned_volume_name) as db: + # check if this is a reference table + table_metadata = db.database.get_table_metadata(table_name) + # convert timestamp string to timestamp object + # with UTC timezone + if isinstance(past_version['time_stamp'], str): + past_version['time_stamp'] = datetime.datetime.fromisoformat( + past_version['time_stamp'] + ) + timestamp = past_version['time_stamp'] + if timestamp.tzinfo is None: + timestamp = pytz.utc.localize(timestamp) + + user_data = { + "table": table_name, + "timestamp": timestamp, + "suffixes": {table_name: ""}, + "desired_resolution": [1, 1, 1], + "limit": 1, + } + + if table_metadata["reference_table"]: + ref_table = table_metadata["reference_table"] + user_data["suffixes"][ref_table] = "_ref" + user_data["join_tables"] = [[table_name, "target_id", ref_table, "id"]] + + return_vals = assemble_live_query_dataframe( + user_data, datastack_name=datastack_name, args={} + ) + df, column_names, mat_warnings, prod_warnings, remap_warnings = return_vals + + relationships = [] + for c in df.columns: + if c.endswith("pt_root_id"): + relationships.append(c) + unique_values = db.database.get_unique_string_values(table_name) + + properties = [] + # find all the columns that start with the same thing + # but end with _pt_position_{x,y,z} + geometry_prefixes, geometry_columns = find_position_prefixes(df) + + for c in df.columns: + if c.endswith("id"): + continue + elif c.endswith("valid"): + continue + elif c.endswith("pt_root_id"): + continue + if c in geometry_columns: + continue + elif c in unique_values.keys(): + prop = viewer_state.AnnotationPropertySpec( + id=c, + type="uint32", + enum_values=np.arange(0, len(unique_values[c])), + enum_labels=sorted(unique_values[c]), + ) + else: + if df[c].dtype == "float64": + type = "float32" + elif df[c].dtype == "int64": + type = "int32" + elif df[c].dtype == "int32": + type = "int32" + else: + continue + prop = viewer_state.AnnotationPropertySpec(id=c, type=type, description=c) + properties.append(prop) + + if len(geometry_prefixes) == 0: + abort(400, "No geometry columns found for table {}".format(table_name)) + if len(geometry_prefixes) == 1: + ann_type = "point" + elif len(geometry_prefixes) == 2: + ann_type = "line" + else: + abort(400, "More than 2 geometry columns found for table {}".format(table_name)) + + return relationships, properties, list(geometry_prefixes), column_names, ann_type, mat_row_count + + +bounds_cache = LRUCache(maxsize=128) + + +@cached(bounds_cache) +def get_precomputed_bounds(datastack_name): + """Get precomputed bounds from the database. + + Args: + datastack_name: The datastack name. + table_name (str): The name of the table. + + Returns: + dict: the bounds for the precomputed table. + """ + ds_info = get_datastack_info(datastack_name) + img_source = ds_info["segmentation_source"] + cv = cloudvolume.CloudVolume(img_source, use_https=True) + bbox = cv.bounds * cv.resolution + lower_bound = bbox.minpt.tolist() + upper_bound = bbox.maxpt.tolist() + return lower_bound, upper_bound + +def _cache_key_spatial_levels(total_size, annotation_count, target_limit=10000): + """Create a hashable cache key for spatial index level calculation.""" + return (tuple(total_size.tolist()), annotation_count, target_limit) + +@cached(LRUCache(maxsize=128), key=_cache_key_spatial_levels) +def calculate_spatial_index_levels(total_size, annotation_count, target_limit=10000): + """ + Calculate the number of spatial index levels needed based on uniform distribution assumption. + + Uses isotropic chunking following Neuroglancer spec: each successive level applies + all subdivisions that improve isotropy compared to the original state. This can + result in multiple dimensions being subdivided simultaneously per level. + + Always adds a final 'spatial_high_res' level with approximately 15000x15000x2000 + chunk sizes for high-resolution queries. + + Args: + total_size: numpy array of [width, height, depth] of the bounding box + annotation_count: total number of annotations + target_limit: maximum annotations per grid cell at finest level + + Returns: + list: List of spatial index level configurations, including final spatial_high_res level + """ + if annotation_count <= target_limit: + # If we have few enough annotations, use overview + high-res levels + levels = [{ + "key": "spatial_overview", + "grid_shape": [1, 1, 1], + "chunk_size": total_size.tolist(), + "limit": target_limit + }] + + return levels + + levels = [] + current_grid_shape = np.array([1, 1, 1], dtype=int) + level = 0 + + while True: + # Calculate chunk size for current grid shape + current_chunk_size = total_size / current_grid_shape + + # Calculate total number of grid cells at this level + total_cells = np.prod(current_grid_shape) + + # Estimate annotations per cell (assuming uniform distribution) + annotations_per_cell = annotation_count / total_cells + + # Add this level + level_key = "spatial_overview" if level == 0 else f"spatial_level_{level}" + levels.append({ + "key": level_key, + "grid_shape": current_grid_shape.tolist(), + "chunk_size": current_chunk_size.astype(int).tolist(), + "limit": target_limit + }) + + # Check if we're fine enough - if average annotations per cell is acceptable + if annotations_per_cell <= target_limit: + break + + # For more isotropic chunking, subdivide the largest dimensions + # that don't make isotropy significantly worse + next_grid_shape = current_grid_shape.copy() + + def calculate_isotropy_metric(chunk_size): + """Calculate isotropy metric - lower is more isotropic.""" + return np.max(chunk_size) / np.min(chunk_size) + + original_isotropy = calculate_isotropy_metric(current_chunk_size) + + made_change = False + # Test subdividing the dimensions with the largest chunk sizes + # but only if it doesn't make isotropy much worse + dim_order = np.argsort(current_chunk_size)[::-1] # Largest first + + for dim in dim_order: + # Test doubling the grid in this dimension (halving chunk size) + test_grid_shape = next_grid_shape.copy() + test_grid_shape[dim] *= 2 + test_chunk_size = total_size / test_grid_shape + test_isotropy = calculate_isotropy_metric(test_chunk_size) + + # Subdivide if it improves isotropy or doesn't make it much worse + # Also prioritize subdividing large dimensions to avoid very elongated chunks + max_chunk = np.max(current_chunk_size) + + # Determine if we should subdivide this dimension + should_subdivide = False + + # Case 1: Dimension is large and isotropy doesn't get too bad + if (current_chunk_size[dim] >= max_chunk * 0.8 and # Dimension is large + test_isotropy <= original_isotropy * 1.5): # Isotropy doesn't get too bad + should_subdivide = True + + # Case 2: Fallback for isotropic volumes - if annotations/cell still too high + # and we have good isotropy, subdivide any dimension that doesn't make it much worse + elif (annotations_per_cell > target_limit * 1.1 and # Still over target + original_isotropy < 2.0 and # Already fairly isotropic + test_isotropy <= original_isotropy * 1.2): # Don't make isotropy much worse + should_subdivide = True + + if should_subdivide: + next_grid_shape[dim] *= 2 + made_change = True + + # If no beneficial subdivision found, stop + if not made_change: + break + + current_grid_shape = next_grid_shape + level += 1 + + # Safety check to prevent infinite loops + if level > 10: + break + + # # Add a final high-resolution level with target chunk size of approximately [15000, 15000, 2000] + # # This level divides the volume into chunks that are suitable for high-resolution queries + # target_chunk_size = np.array([15000, 15000, 2000], dtype=float) + + # # Calculate grid shape needed to achieve target chunk sizes (rounded up) + # high_res_grid_shape = np.maximum([1, 1, 1], np.ceil(total_size / target_chunk_size).astype(int)) + + # # Calculate actual chunk size that divides evenly into the volume + # high_res_chunk_size = total_size / high_res_grid_shape + + # # Calculate total number of grid cells and annotations per cell + # high_res_total_cells = np.prod(high_res_grid_shape) + # high_res_annotations_per_cell = annotation_count / high_res_total_cells + + # # Add the high-resolution level + # levels.append({ + # "key": "spatial_high_res", + # "grid_shape": high_res_grid_shape.tolist(), + # "chunk_size": high_res_chunk_size.astype(int).tolist(), + # "limit": target_limit + # }) + + return levels + + +def get_precomputed_info(datastack_name, table_name): + """Get precomputed properties from the database. + + Args: + datastack_name: The datastack name. + table_name (str): The name of the table. + + Returns: + dict: the info file for the precomputed table. + + Note: + Uses dynamic spatial index level calculation based on annotation distribution + and configurable target limits for optimal Neuroglancer performance. + """ + + + vals = get_precomputed_properties_and_relationships(datastack_name, table_name) + relationships, properties, geometry_columns, column_names, ann_type, mat_row_count = vals + + lower_bound, upper_bound = get_precomputed_bounds(datastack_name) + total_size = np.array(upper_bound) - np.array(lower_bound) + + # Use dynamic spatial index level calculation + target_limit = current_app.config.get("PRECOMPUTED_SPATIAL_INDEX_LIMIT", 10000) + spatial_keys = calculate_spatial_index_levels( + total_size=total_size, + annotation_count=mat_row_count, + target_limit=target_limit + ) + + metadata = { + "@type": "neuroglancer_annotations_v1", + "dimensions": { + "x": [np.float64(1e-09), "m"], + "y": [np.float64(1e-09), "m"], + "z": [np.float64(1e-09), "m"], + }, + "lower_bound": lower_bound, + "upper_bound": upper_bound, + "annotation_type": ann_type, + "properties": [p.to_json() for p in properties], + "relationships": [{"id": r, "key": f"{r}"} for r in relationships], + "by_id": {"key": "by_id"}, + "spatial": spatial_keys + } + return metadata + + +@client_bp.route( + "/datastack//table//precomputed/info" +) +class LiveTablePrecomputedInfo(Resource): + method_decorators = [ + validate_datastack, + limit_by_category("query"), + auth_requires_permission("view", table_arg="datastack_name"), + reset_auth, + ] + + @client_bp.doc("get_precomputed_info", security="apikey") + def get( + self, + datastack_name: str, + table_name: str, + version: int = 0, + target_datastack: str = None, + target_version: int = None, + ): + """get precomputed info for a table + + Args: + datastack_name (str): datastack name + table_name (str): table name + target_datastack (str): target datastack name + target_version (int): target version number + version (int): version number + Returns: + dict: dictionary of precomputed info + """ + # validate_table_args([table_name], target_datastack, target_version) + info = get_precomputed_info(datastack_name, table_name) + + return info, 200 + +def query_spatial_no_filter( + datastack_name: str, + table_name: str, + lower_bound: np.array, + upper_bound: np.array, + timestamp: datetime.datetime = None, + volume_fraction: float = 1.0, + sampling: bool = True, +): + """get precomputed annotation by id + + Args: + datastack_name (str): datastack name + table_name (str): table name + lower_bound (np.array, optional): lower bound of the bounding box. + upper_bound (np.array, optional): upper bound of the bounding box. + Defaults to None in which case will use the bounds of the datastack. + units should be in nanometers + timestamp (datetime.datetime, optional): timestamp to use for the query. + Defaults to None in which case will use the latest timestamp of root_id + volume_fraction (float, optional): fraction of the volume this represents. + sampling (bool, optional): whether to apply spatial sampling. + + Returns: + pd.DataFrame: dataframe of precomputed properties with grid-based spatial sampling + applied to limit results to QUERY_LIMIT_SIZE for better performance + """ + aligned_volume_name, pcg_table_name = get_relevant_datastack_info(datastack_name) + + with request_db_session(aligned_volume_name) as db: + table_metadata = db.database.get_table_metadata(table_name) + + + vals = get_precomputed_properties_and_relationships(datastack_name, table_name) + relationships, properties, d, column_names, ann_type, mat_row_count = vals + + timestamp = datetime.datetime.now(tz=pytz.utc) if timestamp is None else timestamp + + spatial_column = None + spatial_table = None + vox_res = None + + # Find the best spatial column using improved selection logic + def select_best_spatial_column(fields, table_name, metadata): + """Select the best spatial column, prioritizing those with corresponding root_id fields.""" + from materializationengine.utils import make_root_id_column_name + + spatial_columns = [] + all_field_names = set(fields.keys()) + + # Collect all PostGIS fields + for field_name, field in fields.items(): + if isinstance(field, PostGISField): + # Check if this spatial column has a corresponding root_id field + try: + root_id_name = field_name[:-9] + "_root_id" + has_root_id = root_id_name in all_field_names + except (IndexError, AttributeError): + # If root_id name construction fails, assume no root_id + has_root_id = False + + spatial_columns.append({ + 'field_name': field_name, + 'table_name': table_name, + 'metadata': metadata, + 'has_root_id': has_root_id + }) + + # Sort by priority: root_id fields first, then by order found + spatial_columns.sort(key=lambda x: (not x['has_root_id'], x['field_name'])) + + return spatial_columns[0] if spatial_columns else None + + table_schema = db.schema.get_flattened_schema(table_metadata["schema_type"]) + table_fields = table_schema._declared_fields + + # Try to find spatial column in main table + best_spatial = select_best_spatial_column(table_fields, table_name, table_metadata) + if best_spatial: + spatial_column = best_spatial['field_name'] + spatial_table = best_spatial['table_name'] + vox_res = np.array([ + table_metadata["voxel_resolution_x"], + table_metadata["voxel_resolution_y"], + table_metadata["voxel_resolution_z"], + ]) + + user_data = { + "table": table_name, + "timestamp": timestamp, + "suffixes": {table_name: ""}, + "desired_resolution": [1, 1, 1], + } + + if table_metadata["reference_table"]: + ref_table = table_metadata["reference_table"] + user_data["suffixes"][ref_table] = "_ref" + user_data["join_tables"] = [[table_name, "target_id", ref_table, "id"]] + # find the spatial column in the reference table + if spatial_column is None: + # get the reference table schema + ref_metadata = db.database.get_table_metadata(ref_table) + ref_schema = db.schema.get_flattened_schema(ref_metadata["schema_type"]) + ref_fields = ref_schema._declared_fields + + # Use the same improved selection logic for reference table + best_ref_spatial = select_best_spatial_column(ref_fields, ref_table, ref_metadata) + if best_ref_spatial: + spatial_column = best_ref_spatial['field_name'] + spatial_table = best_ref_spatial['table_name'] + vox_res = np.array([ + ref_metadata["voxel_resolution_x"], + ref_metadata["voxel_resolution_y"], + ref_metadata["voxel_resolution_z"], + ]) + + if (spatial_column is None): + abort(400, f"No spatial column found for table {table_name}") + + if lower_bound is not None: + lower_bound = (np.array(lower_bound) / vox_res).astype(int) + upper_bound = (np.array(upper_bound) / vox_res).astype(int) + user_data["filter_spatial_dict"] = { + spatial_table: { + spatial_column: [lower_bound.tolist(), upper_bound.tolist()] + } + } + + # Add hash sampling configuration if we have spatial info + if spatial_column and spatial_table and sampling: + # Get the target limit for spatial sampling + max_points = current_app.config.get("QUERY_LIMIT_SIZE", 10000) + + user_data["hash_sampling_config"] = { + "enabled": True, + "table_name": spatial_table, + "spatial_column": spatial_column, + "volume_fraction": volume_fraction, + "max_points": max_points, + } + + + return_vals = assemble_live_query_dataframe( + user_data, datastack_name=datastack_name, args={} + ) + df, column_names, mat_warnings, prod_warnings, remap_warnings = return_vals + + return df + +def query_by_id( + datastack_name: str, + table_name: str, + annotation_id: int, + timestamp: datetime.datetime = None, +): + """get precomputed annotation by id + + Args: + datastack_name (str): datastack name + table_name (str): table name + annotation_id (int): annotation id + timestamp (datetime.datetime, optional): timestamp to use for the query. + Defaults to None in which case will use the latest timestamp of root_id + + Returns: + pd.DataFrame: dataframe of precomputed properties + """ + aligned_volume_name, pcg_table_name = get_relevant_datastack_info(datastack_name) + with request_db_session(aligned_volume_name) as db: + table_metadata = db.database.get_table_metadata(table_name) + + vals = get_precomputed_properties_and_relationships(datastack_name, table_name) + relationships, properties, geometry_columns, column_names, ann_type, mat_row_count = vals + + timestamp = datetime.datetime.now(tz=pytz.utc) if timestamp is None else timestamp + user_data = { + "table": table_name, + "timestamp": timestamp, + "suffixes": {table_name: ""}, + "filter_equal_dict": { + table_name: {"id": annotation_id}, + }, + "desired_resolution": [1, 1, 1], + } + + if table_metadata["reference_table"]: + ref_table = table_metadata["reference_table"] + user_data["suffixes"][ref_table] = "_ref" + user_data["join_tables"] = [[table_name, "target_id", ref_table, "id"]] + + return_vals = assemble_live_query_dataframe( + user_data, datastack_name=datastack_name, args={} + ) + df, column_names, mat_warnings, prod_warnings, remap_warnings = return_vals + + return df + +def live_query_by_relationship( + datastack_name: str, + table_name: str, + column_name: str, + segid: int, + timestamp: datetime.datetime = None, +): + """get precomputed relationships for a table + + Args: + datastack_name (str): datastack name + table_name (str): table name + column_name (str): column name + segid (int): segment id + timestamp (datetime.datetime, optional): timestamp to use for the query. + Defaults to None in which case will use the latest timestamp of root_id + + + Returns: + pd.DataFrame: dataframe of precomputed relationships + """ + aligned_volume_name, pcg_table_name = get_relevant_datastack_info(datastack_name) + with request_db_session(aligned_volume_name) as db: + table_metadata = db.database.get_table_metadata(table_name) + + vals = get_precomputed_properties_and_relationships(datastack_name, table_name) + relationships, properties, geometry_columns, column_names, ann_type, mat_row_count = vals + + filter_table = None + for table in column_names: + for col in column_names[table]: + if col == column_name: + filter_table = table + break + if filter_table is None: + abort(400, "column_name not found in table {}".format(table_name)) + if timestamp is None: + cg_client = chunkedgraph_cache.get_client(pcg_table_name) + timestamp = cg_client.get_root_timestamps(segid, latest=True)[0] + user_data = { + "table": table_name, + "timestamp": timestamp, + "suffixes": {table_name: ""}, + "filter_equal_dict": { + filter_table: {column_name: segid}, + }, + "desired_resolution": [1, 1, 1], + } + if table_metadata["reference_table"]: + ref_table = table_metadata["reference_table"] + user_data["suffixes"][ref_table] = "_ref" + user_data["join_tables"] = [[table_name, "target_id", ref_table, "id"]] + + return_vals = assemble_live_query_dataframe( + user_data, datastack_name=datastack_name, args={} + ) + df, column_names, mat_warnings, prod_warnings, remap_warnings = return_vals + + return df + + +def format_df_to_bytes(df, datastack_name, table_name, encode_single=False): + """format the dataframe to bytes + + Args: + df (pd.DataFrame): dataframe + datastack_name (str): datastack name + table_name (str): table name + + Returns: + bytes: byte stream of dataframe + """ + vals = get_precomputed_properties_and_relationships(datastack_name, table_name) + relationships, properties, geometry_columns, column_names, anntype, mat_row_count = vals + writer = AnnotationWriter( + anntype, relationships=relationships, properties=properties + ) + if anntype == "point": + point_cols = [ + geometry_columns[0] + "_x", + geometry_columns[0] + "_y", + geometry_columns[0] + "_z", + ] + if anntype == "line": + pointa_cols = [ + geometry_columns[0] + "_x", + geometry_columns[0] + "_y", + geometry_columns[0] + "_z", + ] + pointb_cols = [ + geometry_columns[1] + "_x", + geometry_columns[1] + "_y", + geometry_columns[1] + "_z", + ] + for p in properties: + if p.enum_values is not None: + df[p.id].replace( + {label: val for label, val in zip(p.enum_labels, p.enum_values)}, + inplace=True, + ) + for i, row in df.iterrows(): + kwargs = {p.id: df.loc[i, p.id] for p in properties} + if encode_single: + # update kwargs with relationships + for r in relationships: + if r in df.columns: + kwargs[r] = df.loc[i, r] + if row["valid"]: + if anntype == "point": + point = df.loc[i, point_cols].tolist() + writer.add_point(point, id=df.loc[i, "id"], **kwargs) + elif anntype == "line": + point_a = df.loc[i, pointa_cols].tolist() + point_b = df.loc[i, pointb_cols].tolist() + writer.add_line(point_a, point_b, id=df.loc[i, "id"], **kwargs) + if encode_single: + output_bytes = writer._encode_single_annotation(writer.annotations[0]) + else: + output_bytes = writer._encode_multiple_annotations(writer.annotations) + + return output_bytes + +@client_bp.route( + "/datastack//table/" +) +class LiveTablesAvailable(Resource): + method_decorators = [ + validate_datastack, + auth_requires_permission("view", table_arg="datastack_name"), + reset_auth, + ] + + @client_bp.doc("get_live_tables", security="apikey") + def get(self, datastack_name: str, version: int =-1, target_datastack: str = None, target_version: int =None, **args): + """get live tables for a datastack + + Args: + datastack_name (str): datastack name + + Returns: + HTML directory listing of available tables + """ + aligned_volume_name, pcg_table_name = get_relevant_datastack_info( + datastack_name + ) + with db_manager.session_scope(aligned_volume_name) as session: + version = session.query(AnalysisVersion).filter( + AnalysisVersion.datastack == target_datastack + ).order_by( + AnalysisVersion.time_stamp.desc() + ).first() + if version is not None: + tables = session.query(AnalysisTable).filter( + AnalysisTable.analysisversion_id == version.id, + AnalysisTable.valid == True + ).all() + tables = [table.table_name for table in tables] + + # Generate HTML directory listing + html_content = f""" + + + Index of /datastack/{datastack_name}/table/ + + + +

Index of /datastack/{datastack_name}/table/

+ [Parent Directory] +""" + + # Add each table as a directory entry + for table in sorted(tables): + html_content += f' {table}/\n' + + html_content += """ +""" + + return Response(html_content, mimetype='text/html') + + + +@client_bp.route( + "/datastack//table//precomputed/" +) +class LiveTablesAvailable(Resource): + method_decorators = [ + auth_requires_permission("view", table_arg="datastack_name"), + reset_auth, + ] + + @client_bp.doc("get_info_listing", security="apikey") + def get(self, datastack_name: str, table_name: str): + """get info listing + + Args: + datastack_name (str): datastack name + table_name (str): table name + + Returns: + HTML directory pointing to info file + """ + + # Generate HTML directory listing + html_content = f""" + + + Index of /datastack/{datastack_name}/table/ + + + +

Index of /datastack/{datastack_name}/table/{table_name}/precomputed/

+ [Parent Directory] +""" + + # Add each table as a directory entry + html_content += f' info/\n' + html_content += f' by_id/\n' + html_content += f' spatial/\n' + html_content += """ +""" + + return Response(html_content, mimetype='text/html') + + +# General spatial endpoint that handles all spatial levels (spatial_overview, spatial_level_1, spatial_level_2, etc.) +# This replaces the old spatial_high_res endpoint and provides a unified interface for all spatial levels + +# Cache for spatial query results with 20-minute TTL +_spatial_bytes_cache = TTLCache(maxsize=1000, ttl=1200) # 20 minutes = 1200 seconds + +def _cache_key_spatial_bytes(datastack_name, table_name, spatial_level, x_bin, y_bin, z_bin, timestamp): + """Generate cache key for spatial bytes result.""" + # Round timestamp to minute precision to improve cache hit rate + timestamp_str = None + if timestamp is not None: + timestamp_rounded = timestamp.replace(second=0, microsecond=0) + timestamp_str = timestamp_rounded.isoformat() + + return hashkey(datastack_name, table_name, spatial_level, x_bin, y_bin, z_bin, timestamp_str) + +@client_bp.route( + "/datastack//table//precomputed//__" +) +class LiveTableSpatialLevel(Resource): + method_decorators = [ + validate_datastack, + auth_requires_permission("view", table_arg="datastack_name"), + reset_auth, + ] + + @client_bp.doc("get_precomputed spatial level cutout", security="apikey") + def get(self, datastack_name: str, table_name: str, spatial_level: str, x_bin: int, y_bin: int, z_bin: int, version: int = 0, target_datastack: str = None, target_version: int = None): + """get precomputed spatial cutout for a table at any spatial level + + This is a general endpoint that works with all spatial levels defined in the + precomputed info. It dynamically determines the chunk size and grid bounds + based on the spatial level configuration. + + Args: + datastack_name (str): datastack name + table_name (str): table name + spatial_level (str): spatial level key (e.g., 'spatial_overview', 'spatial_level_1', 'spatial_level_2', etc.) + Must match a key from the spatial index configuration + x_bin (int): x bin index for spatial grid coordinate (0-based) + y_bin (int): y bin index for spatial grid coordinate (0-based) + z_bin (int): z bin index for spatial grid coordinate (0-based) + version (int): version number (ignored) + target_datastack (str): target datastack name (ignored) + target_version (int): target version number (ignored) + + Query Parameters: + None - Intelligent spatial sampling is automatically applied based on the + volume fraction and grid density. Fine-grained chunks use less sampling, + while coarse chunks use more aggressive sampling for optimal performance. + + Returns: + bytes: byte stream of precomputed spatial data with adaptive sampling + + Example URLs: + .../precomputed/spatial_overview/0_0_0 + .../precomputed/spatial_level_1/0_0_0 + .../precomputed/spatial_level_1/1_2_0 + .../precomputed/spatial_level_2/4_3_1 + """ + precomputed_info = get_precomputed_info(datastack_name, table_name) + + aligned_volume_name, pcg_table_name = get_relevant_datastack_info( + target_datastack + ) + if version is not None: + with db_manager.session_scope(aligned_volume_name) as session: + analysis_version = session.query(AnalysisVersion).filter( + AnalysisVersion.datastack == datastack_name, + AnalysisVersion.version == version, + ).one_or_none() + timestamp = analysis_version.time_stamp.astimezone(datetime.timezone.utc) if analysis_version else None + else: + timestamp = None + + # get the info for this spatial index from the precomputed info + spatial_keys = precomputed_info.get("spatial", []) + spatial_key = None + for spatial_index in spatial_keys: + if spatial_index["key"] == spatial_level: + spatial_key = spatial_index + break + + if spatial_key is None: + abort(404, f"Spatial level '{spatial_level}' not found for table '{table_name}'") + + # Validate grid coordinates are within valid bounds + grid_shape = spatial_key.get("grid_shape", [1, 1, 1]) + if (x_bin < 0 or x_bin >= grid_shape[0] or + y_bin < 0 or y_bin >= grid_shape[1] or + z_bin < 0 or z_bin >= grid_shape[2]): + abort(400, f"Grid coordinates ({x_bin}, {y_bin}, {z_bin}) are out of bounds for spatial level '{spatial_level}' with grid shape {grid_shape}") + + lower_bound, upper_bound = get_precomputed_bounds(datastack_name) + + chunk_size = np.array(spatial_key["chunk_size"]) + grid_shape = np.array(spatial_key["grid_shape"]) + + # Calculate what fraction of the total volume this chunk represents + total_volume = np.prod(np.array(upper_bound) - np.array(lower_bound)) + chunk_volume = np.prod(chunk_size) + + # Validate volume calculations to prevent invalid tablesample parameters + if total_volume <= 0: + print(f"WARNING: Invalid total_volume: {total_volume}, bounds: {lower_bound} to {upper_bound}") + volume_fraction = 1.0 # Fallback to no sampling + elif chunk_volume <= 0: + print(f"WARNING: Invalid chunk_volume: {chunk_volume}, chunk_size: {chunk_size}") + volume_fraction = 1.0 # Fallback to no sampling + else: + volume_fraction = chunk_volume / total_volume + + # Ensure volume_fraction is valid for tablesample + if not np.isfinite(volume_fraction) or volume_fraction <= 0: + print(f"WARNING: Invalid volume_fraction: {volume_fraction}, defaulting to 1.0") + volume_fraction = 1.0 + elif volume_fraction > 1.0: + print(f"WARNING: volume_fraction > 1.0: {volume_fraction}, capping at 1.0") + volume_fraction = 1.0 + + # get the lower and upper bounds of this grid + lower_bound = np.array(lower_bound) + np.array( + [x_bin * chunk_size[0], y_bin * chunk_size[1], z_bin * chunk_size[2]] + ) + upper_bound = lower_bound + chunk_size + if "high_res" in spatial_level: + sampling=False + else: + sampling = True + + # Check cache first for this specific spatial chunk + cache_key = _cache_key_spatial_bytes(datastack_name, table_name, spatial_level, x_bin, y_bin, z_bin, timestamp) + + if cache_key in _spatial_bytes_cache: + bytes_data = _spatial_bytes_cache[cache_key] + else: + # Query and format data if not in cache + df = query_spatial_no_filter(datastack_name, + table_name, + lower_bound, + upper_bound, + timestamp, + volume_fraction=volume_fraction, + sampling=sampling) + + bytes_data = format_df_to_bytes(df, datastack_name, table_name) + + # Cache the result + _spatial_bytes_cache[cache_key] = bytes_data + + response= Response(bytes_data, mimetype='application/octet-stream') + response.headers["Content-Disposition"] = ( + f"attachment; filename={datastack_name}_{table_name}_{spatial_level}_{x_bin}_{y_bin}_{z_bin}.bin" + ) + headers = { + "access-control-allow-credentials": "true", + "access-control-expose-headers": "Cache-Control, Content-Disposition, Content-Encoding, Content-Length, Content-Type, Date, ETag, Server, Vary, X-Content-Type-Options, X-Frame-Options, X-Powered-By, X-XSS-Protection", + "content-disposition": "attachment", + "Content-Type": "application/octet-stream", + "Content-Name": f"{datastack_name}_{table_name}_{spatial_level}_{x_bin}_{y_bin}_{z_bin}.bin", + } + response.headers.update(headers) + return response + + +# # Backward compatibility endpoint for spatial_overview at coordinates 0_0_0 +# # New code should use the general spatial endpoint: .../precomputed/spatial_overview/0_0_0 +# @client_bp.route( +# "/datastack//table//precomputed/spatial_overview/0_0_0" +# ) +# class LiveTableSpatialOverview(Resource): +# method_decorators = [ +# validate_datastack, +# auth_requires_permission("view", table_arg="datastack_name"), +# reset_auth, +# ] + +# @client_bp.doc("get_precomputed_overview", security="apikey") +# def get(self, datastack_name: str, table_name: str, version: int = 0, target_datastack: str = None, target_version: int = None): +# """get precomputed spatial overview for a table + +# Args: +# datastack_name (str): datastack name +# table_name (str): table name +# version (int): version number (ignored) +# target_datastack (str): target datastack name (ignored) +# target_version (int): target version number (ignored) + +# Query Parameters: +# None - grid-based spatial sampling is automatically applied using +# QUERY_LIMIT_SIZE from Flask config to ensure good performance + +# Returns: +# bytes: byte stream of precomputed spatial overview with representative sampling +# """ + +# aligned_volume_name, pcg_table_name = get_relevant_datastack_info( +# target_datastack +# ) +# if version is not None: +# with db_manager.session_scope(aligned_volume_name) as session: +# analysis_version = session.query(AnalysisVersion).filter( +# AnalysisVersion.datastack == datastack_name, +# AnalysisVersion.version == version, +# ).one_or_none() +# timestamp = analysis_version.time_stamp.astimezone(datetime.timezone.utc) if analysis_version else None +# else: +# timestamp = None + +# lower_bound, upper_bound = get_precomputed_bounds(datastack_name) + +# df = query_spatial_no_filter(datastack_name, table_name, None, None, timestamp) + +# bytes = format_df_to_bytes(df, datastack_name, table_name) + +# response= Response(bytes, mimetype='application/octet-stream') +# response.headers["Content-Disposition"] = ( +# f"attachment; filename={datastack_name}_{table_name}_spatial_overview.bin" +# ) +# headers = { +# "access-control-allow-credentials": "true", +# "access-control-expose-headers": "Cache-Control, Content-Disposition, Content-Encoding, Content-Length, Content-Type, Date, ETag, Server, Vary, X-Content-Type-Options, X-Frame-Options, X-Powered-By, X-XSS-Protection", +# "content-disposition": "attachment", +# "Content-Type": "application/octet-stream", +# "Content-Name": f"{datastack_name}_{table_name}_spatial_overview.bin", +# } +# response.headers.update(headers) +# return response + +@client_bp.route( + "/datastack//table//precomputed/by_id/" +) +class LiveTablePrecomputedById(Resource): + method_decorators = [ + validate_datastack, + auth_requires_permission("view", table_arg="datastack_name"), + reset_auth, + ] + + @client_bp.doc("get_precomputed_by_id", security="apikey") + def get(self, datastack_name: str, table_name: str, id: int, version: int = 0, target_datastack: str = None, target_version: int = None): + """get precomputed by_id for a table + + Args: + datastack_name (str): datastack name + table_name (str): table name + id (int): annotation id + version (int): version number (ignored) + target_datastack (str): target datastack name (ignored) + target_version (int): target version number (ignored) + + Returns: + bytes: byte stream of precomputed by_id + """ + + aligned_volume_name, pcg_table_name = get_relevant_datastack_info( + target_datastack + ) + if version is not None: + with db_manager.session_scope(aligned_volume_name) as session: + analysis_version = session.query(AnalysisVersion).filter( + AnalysisVersion.datastack == datastack_name, + AnalysisVersion.version == version, + ).one_or_none() + timestamp = analysis_version.time_stamp.astimezone(datetime.timezone.utc) if analysis_version else None + else: + timestamp = None + + df = query_by_id(datastack_name, table_name, id, timestamp) + + bytes = format_df_to_bytes(df, datastack_name, table_name, encode_single=True) + + response= Response(bytes, mimetype='application/octet-stream') + response.headers["Content-Disposition"] = ( + f"attachment; filename={datastack_name}_{table_name}_{id}.bin" + ) + headers = { + "access-control-allow-credentials": "true", + "access-control-expose-headers": "Cache-Control, Content-Disposition, Content-Encoding, Content-Length, Content-Type, Date, ETag, Server, Vary, X-Content-Type-Options, X-Frame-Options, X-Powered-By, X-XSS-Protection", + "content-disposition": "attachment", + "Content-Type": "application/octet-stream", + "Content-Name": f"{datastack_name}_{table_name}_{id}.bin", + } + response.headers.update(headers) + return response + +@client_bp.route( + "/datastack//table//precomputed//" +) +class LiveTablePrecomputedRelationship(Resource): + method_decorators = [ + validate_datastack, + auth_requires_permission("view", table_arg="datastack_name"), + reset_auth, + ] + + @client_bp.doc("get_precomputed_relationships", security="apikey") + def get(self, datastack_name: str, table_name: str, column_name: str, segid: int, version: int = 0, target_datastack: str = None, target_version: int = None): + """get precomputed relationships for a table + + Args: + datastack_name (str): datastack name + table_name (str): table name + column_name (str): column name + segid (int): segment id + version (int): version number (ignored) + target_datastack (str): target datastack name (ignored) + target_version (int): target version number (ignored) + + Returns: + bytes: byte stream of precomputed relationships + """ + if not column_name.endswith("pt_root_id"): + abort(400, "column_name must end with pt_root_id") + + aligned_volume_name, pcg_table_name = get_relevant_datastack_info( + target_datastack + ) + if version is not None: + with db_manager.session_scope(aligned_volume_name) as session: + analysis_version = session.query(AnalysisVersion).filter( + AnalysisVersion.datastack == datastack_name, + AnalysisVersion.version == version, + ).one_or_none() + timestamp = analysis_version.time_stamp.astimezone(datetime.timezone.utc) if analysis_version else None + else: + timestamp = None + df = live_query_by_relationship(datastack_name, table_name, column_name, segid, timestamp) + bytes = format_df_to_bytes(df, datastack_name, table_name) + + # format a flask response with bytes as raw bytes conent + response = Response(bytes, status=200, mimetype="application/octet-stream") + response.headers["Content-Disposition"] = ( + f"attachment; filename={datastack_name}_{table_name}_{column_name}_{segid}.bin" + ) + headers = { + "access-control-allow-credentials": "true", + "access-control-expose-headers": "Cache-Control, Content-Disposition, Content-Encoding, Content-Length, Content-Type, Date, ETag, Server, Vary, X-Content-Type-Options, X-Frame-Options, X-Powered-By, X-XSS-Protection", + "content-disposition": "attachment", + "Content-Type": "application/octet-stream", + "Content-Name": f"{datastack_name}_{table_name}_{column_name}_{segid}.bin", + } + response.headers.update(headers) + + return response + + @client_bp.expect(query_parser) @client_bp.route( "/datastack//version//views" @@ -1807,10 +3080,9 @@ def get( mat_db_name = f"{aligned_volume_name}" else: mat_db_name = f"{datastack_name}__mat{version}" - - meta_db = dynamic_annotation_cache.get_db(mat_db_name) - views = meta_db.database.get_views(datastack_name) - views = AnalysisViewSchema().dump(views, many=True) + with request_db_session(mat_db_name) as meta_db: + views = meta_db.database.get_views(datastack_name) + views = AnalysisViewSchema().dump(views, many=True) view_d = {} for view in views: name = view.pop("table_name") @@ -1856,9 +3128,8 @@ def get( mat_db_name = f"{aligned_volume_name}" else: mat_db_name = f"{datastack_name}__mat{version}" - - meta_db = dynamic_annotation_cache.get_db(mat_db_name) - md = meta_db.database.get_view_metadata(datastack_name, view_name) + with request_db_session(mat_db_name) as meta_db: + md = meta_db.database.get_view_metadata(datastack_name, view_name) return md @@ -1896,9 +3167,8 @@ def assemble_view_dataframe(datastack_name, version, view_name, data, args): get_count = args.get("count", False) if get_count: limit = None - - mat_db = dynamic_annotation_cache.get_db(mat_db_name) - md = mat_db.database.get_view_metadata(datastack_name, view_name) + with request_db_session(mat_db_name) as mat_db: + md = mat_db.database.get_view_metadata(datastack_name, view_name) if not data.get("desired_resolution", None): des_res = [ @@ -2030,7 +3300,6 @@ def get( if version == -1: version = get_latest_version(datastack_name) - print(f"using version {version}") mat_db_name = f"{datastack_name}__mat{version}" if version == 0: mat_db_name = f"{aligned_volume_name}" @@ -2141,11 +3410,12 @@ def post( } }, "filter_spatial_dict": { - "tablename": { - "column_name": [[min_x, min_y, min_z], [max_x, max_y, max_z]] + "tablename":{ + "column_name":[[min_x,min_y,min_z], [max_x,max_y,max_z]] + } }, "filter_regex_dict": { - "tablename": { + "tablename":{ "column_name": "regex" } } @@ -2247,9 +3517,8 @@ def get( mat_db_name = f"{aligned_volume_name}" else: mat_db_name = f"{datastack_name}__mat{version}" - - meta_db = dynamic_annotation_cache.get_db(mat_db_name) - table = meta_db.database.get_view_table(view_name) + with request_db_session(mat_db_name) as meta_db: + table = meta_db.database.get_view_table(view_name) return get_table_schema(table) @@ -2291,9 +3560,8 @@ def get( mat_db_name = f"{aligned_volume_name}" else: mat_db_name = f"{datastack_name}__mat{version}" - - meta_db = dynamic_annotation_cache.get_db(mat_db_name) - views = meta_db.database.get_views(datastack_name) + with request_db_session(mat_db_name) as meta_db: + views = meta_db.database.get_views(datastack_name) if not views: return {}, 404 diff --git a/materializationengine/blueprints/client/common.py b/materializationengine/blueprints/client/common.py index f6f09dde..64a20d32 100644 --- a/materializationengine/blueprints/client/common.py +++ b/materializationengine/blueprints/client/common.py @@ -312,8 +312,12 @@ def generate_simple_query_dataframe( qm.apply_filter(data.get("filter_equal_dict", None), qm.apply_equal_filter) qm.apply_filter(data.get("filter_greater_dict", None), qm.apply_greater_filter) qm.apply_filter(data.get("filter_less_dict", None), qm.apply_less_filter) - qm.apply_filter(data.get("filter_greater_equal_dict", None), qm.apply_greater_equal_filter) - qm.apply_filter(data.get("filter_less_equal_dict", None), qm.apply_less_equal_filter) + qm.apply_filter( + data.get("filter_greater_equal_dict", None), qm.apply_greater_equal_filter + ) + qm.apply_filter( + data.get("filter_less_equal_dict", None), qm.apply_less_equal_filter + ) qm.apply_filter(data.get("filter_spatial_dict", None), qm.apply_spatial_filter) qm.apply_filter(data.get("filter_regex_dict", None), qm.apply_regex_filter) qm.apply_filter({table_name: {"valid": True}}, qm.apply_equal_filter) @@ -474,8 +478,12 @@ def generate_complex_query_dataframe( qm.apply_filter(data.get("filter_equal_dict", None), qm.apply_equal_filter) qm.apply_filter(data.get("filter_greater_dict", None), qm.apply_greater_filter) qm.apply_filter(data.get("filter_less_dict", None), qm.apply_less_filter) - qm.apply_filter(data.get("filter_greater_equal_dict", None), qm.apply_greater_equal_filter) - qm.apply_filter(data.get("filter_less_equal_dict", None), qm.apply_less_equal_filter) + qm.apply_filter( + data.get("filter_greater_equal_dict", None), qm.apply_greater_equal_filter + ) + qm.apply_filter( + data.get("filter_less_equal_dict", None), qm.apply_less_equal_filter + ) qm.apply_filter(data.get("filter_spatial_dict", None), qm.apply_spatial_filter) qm.apply_filter(data.get("filter_regex_dict", None), qm.apply_regex_filter) for table_info in data["tables"]: diff --git a/materializationengine/blueprints/client/datastack.py b/materializationengine/blueprints/client/datastack.py index d132ba85..de1ffc1d 100644 --- a/materializationengine/blueprints/client/datastack.py +++ b/materializationengine/blueprints/client/datastack.py @@ -35,6 +35,8 @@ def wrapper(*args, **kwargs): with db_manager.session_scope(aligned_volume_name) as session: version_query = session.query(AnalysisVersion).filter( AnalysisVersion.datastack == target_datastack + ).order_by( + AnalysisVersion.time_stamp.desc() ) if target_version: if target_version == 0: diff --git a/materializationengine/blueprints/client/precomputed.py b/materializationengine/blueprints/client/precomputed.py new file mode 100644 index 00000000..afbf97d7 --- /dev/null +++ b/materializationengine/blueprints/client/precomputed.py @@ -0,0 +1,287 @@ +import numbers +from typing import Literal, NamedTuple, Optional, Union, cast +from collections.abc import Sequence +import struct +from neuroglancer import coordinate_space, viewer_state +import numpy as np + + +class Annotation(NamedTuple): + id: int + encoded: bytes + relationships: Sequence[Sequence[int]] + + +_PROPERTY_DTYPES: dict[ + str, tuple[Union[tuple[str], tuple[str, tuple[int, ...]]], int] +] = { + "uint8": (("|u1",), 1), + "uint16": ((" 0: + # Ensure the random sample is a valid percentage (convert to float if needed) + try: + sample_value = float(self._random_sample) + if not np.isfinite(sample_value) or sample_value <= 0 or sample_value > 100: + # Skip TABLESAMPLE if invalid + annmodel_alias1 = annmodel + else: + annmodel_alias1 = aliased( + annmodel, tablesample(annmodel, sample_value) + ) + except (ValueError, TypeError): + # Skip TABLESAMPLE if conversion fails + annmodel_alias1 = annmodel else: annmodel_alias1 = annmodel subquery = ( @@ -180,23 +190,41 @@ def add_table(self, table_name, random_sample=False): .join(segmodel, annmodel_alias1.id == segmodel.id, isouter=True) .subquery() ) - annmodel_alias = aliased(subquery, name=table_name, flat=True) + # Use flat=False to preserve parameter binding context when TABLESAMPLE is used + use_flat = not (random_sample and self._random_sample is not None and self._random_sample > 0) + annmodel_alias = aliased(subquery, name=table_name, flat=use_flat) self._models[table_name] = annmodel_alias # self._models[segmodel.__tablename__] = segmodel_alias else: - if random_sample and self._random_sample: - annmodel_alias1 = aliased( - annmodel, tablesample(annmodel, self._random_sample) - ) + if random_sample and self._random_sample is not None and self._random_sample > 0: + # Ensure the random sample is a valid percentage (convert to float if needed) + try: + sample_value = float(self._random_sample) + if not np.isfinite(sample_value) or sample_value <= 0 or sample_value > 100: + abort(500, f"Invalid random_sample value: {sample_value}, skipping TABLESAMPLE") + else: + annmodel_alias1 = aliased( + annmodel, tablesample(annmodel, sample_value) + ) + except (ValueError, TypeError) as e: + abort(500, f"Cannot convert random_sample to float: {self._random_sample}, error: {e}") else: annmodel_alias1 = annmodel self._models[table_name] = annmodel_alias1 else: model = self._get_flat_model(table_name) - if self._random_sample: - model = aliased(model, tablesample, self._random_sample) + if self._random_sample is not None and self._random_sample > 0: + # Ensure the random sample is a valid percentage (convert to float if needed) + try: + sample_value = float(self._random_sample) + if not np.isfinite(sample_value) or sample_value <= 0 or sample_value > 100: + abort(500, f"Invalid random_sample value: {sample_value}, skipping TABLESAMPLE") + else: + model = aliased(model, tablesample(model, sample_value)) + except (ValueError, TypeError) as e: + abort(500, f"Cannot convert random_sample to float: {self._random_sample}, error: {e}") self._models[table_name] = model def _find_relevant_model(self, table_name, column_name): @@ -208,7 +236,6 @@ def _find_relevant_model(self, table_name, column_name): return model def join_tables(self, table1, column1, table2, column2, isouter=False): - self.add_table(table1, random_sample=True) self.add_table(table2) @@ -302,7 +329,6 @@ def select_column(self, table_name, column_name): # if the column_name is not in the table_name list # then we should add it if column_name not in self._selected_columns[table_name]: - model = self._find_relevant_model( table_name=table_name, column_name=column_name ) @@ -459,7 +485,8 @@ def apply_filter(filter_key, filter_func): user_data.get("filter_less_dict", None), self.apply_less_filter ) self.apply_filter( - user_data.get("filter_greater_equal_dict", None), self.apply_greater_equal_filter + user_data.get("filter_greater_equal_dict", None), + self.apply_greater_equal_filter, ) self.apply_filter( user_data.get("filter_less_equal_dict", None), self.apply_less_equal_filter @@ -491,6 +518,7 @@ def _make_query( filter_args: Iterable of iterables select_columns: None or Iterable of str offset: Int offset of query + limit: Int limit of query Returns: SQLAchemy query object @@ -566,7 +594,6 @@ def execute_query(self, desired_resolution=None): ] query_args += column_args else: - if self._split_mode and ( column.key.endswith("_root_id") or column.key.endswith("_supervoxel_id") @@ -604,14 +631,22 @@ def execute_query(self, desired_resolution=None): else: query_args.append(column) + # Apply hash sampling filters before building the query + hash_filters = self._get_hash_sampling_filters() + filters_with_hash = self._filters + hash_filters if hash_filters else self._filters + + # Update limit if grid sampling is enabled + effective_limit = self._get_effective_limit() + query = self._make_query( query_args=self._models.values(), join_args=self._joins, - filter_args=self._filters, + filter_args=filters_with_hash, select_columns=query_args, offset=self.offset, - limit=self.limit, + limit=effective_limit, ) + df = _execute_query( self._db.database.session, self._db.database.engine, @@ -621,3 +656,105 @@ def execute_query(self, desired_resolution=None): get_count=self.get_count, ) return df, column_names + + def add_hash_spatial_sampling(self, table_name: str, spatial_column: str, max_points: int = 10000, total_row_count: int = None): + """ + Configure hash-based spatial sampling for a table to limit result size + while maintaining spatial representativeness. + + Args: + table_name: Name of the table to sample + spatial_column: Name of the spatial geometry column + max_points: Maximum number of points to return + bounds: Optional bounds dictionary with lower_bound, upper_bound, and vox_res + total_row_count: Actual number of rows in the table for accurate sampling calculation + """ + + self._hash_sampling = { + 'table_name': table_name, + 'spatial_column': spatial_column, + 'max_points': max_points, + 'total_row_count': total_row_count # Store the actual row count + } + + def _get_hash_sampling_filters(self): + """ + Get hash sampling filters to be added to the query filters + """ + if not hasattr(self, '_hash_sampling') or not self._hash_sampling: + return [] + + config = self._hash_sampling + table_name = config['table_name'] + spatial_col = config['spatial_column'] + max_points = config.get('max_points', 10000) # Default to 10000 if not specified + total_row_count = config.get('total_row_count', 30_000_000) # Get the actual row count if available + + # Use existing model from the QueryManager to avoid duplicate table references + model = self._models.get(table_name) + if model is None: + return [] # Can't build filter if we don't have the model in our query + + try: + geom_column = get_column(model, spatial_col) + except: + return [] # Can't build filter if we can't get the column + + # Create SQLAlchemy expressions for grid sampling + # Instead of requiring exact grid intersections, we'll use a subquery approach + # that selects a limited number of points from each grid cell + + # For now, let's use a simpler approach: sample points that fall into + # specific grid cells using integer division to assign grid coordinates + # This is more likely to return actual results + + # Use hash-based sampling for better spatial distribution without visible grid patterns + # This approach hashes the geometry coordinates to get a pseudo-random but deterministic sample + + # Calculate the sampling ratio based on actual table size and desired max_points + # Add validation to prevent division by zero or invalid values + if max_points <= 0: + print(f"WARNING: Invalid max_points: {max_points}, using default 10000") + max_points = 10000 + + if total_row_count and total_row_count > 0: + # Use actual row count for precise sampling calculation + sampling_factor = max(2, int(total_row_count / max_points)) + else: + # Fallback to estimate if row count is not available + sampling_factor = max(2, int(30_000_000 / max_points)) + + # Additional validation to ensure sampling_factor is valid + if sampling_factor <= 0 or not np.isfinite(sampling_factor): + print(f"WARNING: Invalid sampling_factor: {sampling_factor}, using default 10") + sampling_factor = 10 + + # Use MD5 hash of the geometry text representation for pseudo-random sampling + # Use a simpler approach that avoids potential hex parsing issues + geom_text = func.ST_AsText(geom_column) + geom_hash = func.md5(geom_text) + + # Use the first few characters of the hash and convert each character to its ASCII value + # This is more reliable than trying to parse hex directly + # Take first 4 characters and sum their ASCII values for a pseudo-random integer + char1 = func.ascii(func.substr(geom_hash, 1, 1)) + char2 = func.ascii(func.substr(geom_hash, 2, 1)) + char3 = func.ascii(func.substr(geom_hash, 3, 1)) + char4 = func.ascii(func.substr(geom_hash, 4, 1)) + + # Create a pseudo-random integer from ASCII values + hash_as_int = char1 * 1000 + char2 * 100 + char3 * 10 + char4 + + # Use modulo to get uniform distribution + grid_filter = func.mod(hash_as_int, sampling_factor) == 0 + + return [(grid_filter,)] + + def _get_effective_limit(self): + """ + Get the effective limit considering hash sampling + """ + if hasattr(self, '_hash_sampling') and self._hash_sampling: + self.limit = None + + return self.limit diff --git a/materializationengine/blueprints/client/utils.py b/materializationengine/blueprints/client/utils.py index 2984ec4e..070195dc 100644 --- a/materializationengine/blueprints/client/utils.py +++ b/materializationengine/blueprints/client/utils.py @@ -49,7 +49,6 @@ def after_request(response): def add_warnings_to_headers(headers, warnings): if len(warnings) > 0: warnings = [w.replace("\n", " ") for w in warnings] - print(warnings) headers["Warning"] = warnings return headers diff --git a/materializationengine/blueprints/materialize/api.py b/materializationengine/blueprints/materialize/api.py index 0613b641..1a38dace 100644 --- a/materializationengine/blueprints/materialize/api.py +++ b/materializationengine/blueprints/materialize/api.py @@ -19,7 +19,7 @@ from dynamicannotationdb.models import AnalysisVersion from materializationengine.schemas import AnalysisTableSchema, AnalysisVersionSchema from materializationengine.blueprints.materialize.schemas import BadRootsSchema -from middle_auth_client import auth_requires_admin, auth_requires_permission +from middle_auth_client import auth_requires_admin, auth_requires_permission, auth_requires_dataset_admin from sqlalchemy import MetaData, Table from sqlalchemy.engine.url import make_url from sqlalchemy.exc import NoSuchTableError @@ -35,7 +35,7 @@ ) -__version__ = "5.0.1" +__version__ = "5.11.0" bulk_upload_parser = reqparse.RequestParser() @@ -412,7 +412,7 @@ def post(self, datastack_name: str): ) class DumpTableToBucketAsCSV(Resource): @reset_auth - @auth_requires_admin + @auth_requires_dataset_admin(table_arg="datastack_name") @mat_bp.doc("Take table or view and dump it to a bucket as csv", security="apikey") @mat_bp.response(200, "Success", response_model) @mat_bp.response(500, "Internal Server Error", response_model) @@ -425,6 +425,7 @@ def post(self, datastack_name: str, version: int, table_name: str): table_name (str): name of table or view to dump """ mat_db_name = f"{datastack_name}__mat{version}" + # get the segmentation table name of the table_name # TODO: add validation of parameters sql_instance_name = current_app.config.get("SQL_INSTANCE_NAME", None) diff --git a/materializationengine/celery_worker.py b/materializationengine/celery_worker.py index d4547a4e..c5ea9590 100644 --- a/materializationengine/celery_worker.py +++ b/materializationengine/celery_worker.py @@ -32,7 +32,26 @@ def create_celery(app=None): celery.conf.result_backend_transport_options = { "master_name": app.config["MASTER_NAME"] } - + # Configure Celery and related loggers + log_level = app.config["LOGGING_LEVEL"] + celery_logger.setLevel(log_level) + + # Configure all Celery internal loggers to suppress noisy messages + celery_loggers = [ + 'celery', + 'celery.worker', + 'celery.worker.consumer', + 'celery.worker.strategy', + 'celery.worker.heartbeat', + 'celery.worker.job', + 'celery.beat', + 'celery.control', + 'celery.app.trace' + ] + + for logger_name in celery_loggers: + logging.getLogger(logger_name).setLevel(log_level) + celery.conf.update( { "task_routes": ("materializationengine.task_router.TaskRouter"), @@ -74,12 +93,10 @@ def __call__(self, *args, **kwargs): @after_setup_logger.connect def celery_loggers(logger, *args, **kwargs): """ - Display the Celery banner appears in the log output. - https://www.distributedpython.com/2018/10/01/celery-docker-startup/ + Add stdout handler for Celery logger output. """ - logger.info(f"Customize Celery logger, default handler: {logger.handlers[0]}") logger.addHandler(logging.StreamHandler(sys.stdout)) - + def days_till_next_month(date): """function to pick out the same weekday in the next month diff --git a/materializationengine/config.py b/materializationengine/config.py index 76af5a88..6a939dab 100644 --- a/materializationengine/config.py +++ b/materializationengine/config.py @@ -15,7 +15,7 @@ class BaseConfig: TESTING = False LOGGING_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" LOGGING_LOCATION = HOME + "/.materializationengine/bookshelf.log" - LOGGING_LEVEL = logging.DEBUG + LOGGING_LEVEL = logging.WARNING SQLALCHEMY_DATABASE_URI = "sqlite://" SQLALCHEMY_TRACK_MODIFICATIONS = False REDIS_URL = "redis://" @@ -32,6 +32,9 @@ class BaseConfig: MASTER_NAME = os.environ.get("MASTER_NAME", None) MATERIALIZATION_ROW_CHUNK_SIZE = 500 QUERY_LIMIT_SIZE = 200000 + PRECOMPUTED_OVERVIEW_MAX_SIZE = 10000 + PRECOMPUTED_SPATIAL_INDEX_LIMIT = 10000 + HASH_SAMPLING_THRESHOLD_PERCENT = 5.0 QUEUE_LENGTH_LIMIT = 10000 QUEUES_TO_THROTTLE = ["process"] THROTTLE_QUEUES = True @@ -65,8 +68,8 @@ class BaseConfig: else: AUTH_TOKEN = "" - DB_CONNECTION_POOL_SIZE = 5 - DB_CONNECTION_MAX_OVERFLOW = 5 + DB_CONNECTION_POOL_SIZE = 20 + DB_CONNECTION_MAX_OVERFLOW = 30 BEAT_SCHEDULES = [ { @@ -165,7 +168,7 @@ class TestConfig(BaseConfig): class ProductionConfig(BaseConfig): ENV = "production" - LOGGING_LEVEL = logging.INFO + LOGGING_LEVEL = logging.WARNING CELERY_BROKER = os.environ.get("REDIS_URL") CELERY_RESULT_BACKEND = os.environ.get("REDIS_URL") REDIS_URL = os.environ.get("REDIS_URL") diff --git a/materializationengine/database.py b/materializationengine/database.py index bf906dcf..3c38ea60 100644 --- a/materializationengine/database.py +++ b/materializationengine/database.py @@ -3,7 +3,7 @@ from dynamicannotationdb import DynamicAnnotationInterface from flask import current_app -from sqlalchemy import MetaData, create_engine +from sqlalchemy import MetaData, create_engine, text from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.pool import QueuePool @@ -60,21 +60,39 @@ def get_engine(self, database_name: str): sql_base_uri = SQL_URI_CONFIG.rpartition("/")[0] sql_uri = f"{sql_base_uri}/{database_name}" - pool_size = current_app.config.get("DB_CONNECTION_POOL_SIZE", 5) - max_overflow = current_app.config.get("DB_CONNECTION_MAX_OVERFLOW", 5) + pool_size = current_app.config.get("DB_CONNECTION_POOL_SIZE", 20) + max_overflow = current_app.config.get("DB_CONNECTION_MAX_OVERFLOW", 30) - self._engines[database_name] = create_engine( - sql_uri, - poolclass=QueuePool, - pool_size=pool_size, - max_overflow=max_overflow, - pool_timeout=30, - pool_recycle=1800, # Recycle connections after 30 minutes - pool_pre_ping=True, # Ensure connections are still valid - ) - - celery_logger.info(f"Created new connection pool for {database_name} " - f"(size={pool_size}, max_overflow={max_overflow})") + try: + engine = create_engine( + sql_uri, + poolclass=QueuePool, + pool_size=pool_size, + max_overflow=max_overflow, + pool_timeout=30, + pool_recycle=1800, # Recycle connections after 30 minutes + pool_pre_ping=True, # Ensure connections are still valid + ) + + # Test the connection to make sure the database exists and is accessible + with engine.connect() as conn: + conn.execute(text("SELECT 1")) + + # Only store engine if connection test passes + self._engines[database_name] = engine + celery_logger.info(f"Created new connection pool for {database_name} " + f"(size={pool_size}, max_overflow={max_overflow})") + + except Exception as e: + # Clean up engine if it was created but connection failed + if 'engine' in locals(): + engine.dispose() + + celery_logger.error(f"Failed to create/connect to database {database_name}: {e}") + raise ConnectionError(f"Cannot connect to database '{database_name}'. " + f"Please check if the database exists and is accessible. " + f"Connection URI: {sql_uri}. " + f"Error: {e}") return self._engines[database_name] @@ -112,13 +130,13 @@ def session_scope(self, database_name: str): def cleanup(self): """Cleanup any remaining sessions and dispose of engine pools.""" - for database_name, session_factory in self._session_factories.items(): + for database_name, session_factory in list(self._session_factories.items()): try: session_factory.remove() except Exception as e: celery_logger.error(f"Error cleaning up sessions for {database_name}: {e}") - for database_name, engine in self._engines.items(): + for database_name, engine in list(self._engines.items()): try: engine.dispose() except Exception as e: @@ -158,8 +176,8 @@ def get_db(self, database: str) -> DynamicAnnotationInterface: def _get_mat_client(self, database: str): sql_uri_config = get_config_param("SQLALCHEMY_DATABASE_URI") - pool_size = current_app.config.get("DB_CONNECTION_POOL_SIZE", 5) - max_overflow = current_app.config.get("DB_CONNECTION_MAX_OVERFLOW", 5) + pool_size = current_app.config.get("DB_CONNECTION_POOL_SIZE", 20) + max_overflow = current_app.config.get("DB_CONNECTION_MAX_OVERFLOW", 30) mat_client = DynamicAnnotationInterface( sql_uri_config, database, pool_size, max_overflow ) diff --git a/materializationengine/request_db.py b/materializationengine/request_db.py new file mode 100644 index 00000000..8024e624 --- /dev/null +++ b/materializationengine/request_db.py @@ -0,0 +1,86 @@ +""" +Request-scoped database connection management. + +This module provides utilities for managing database connections at the Flask request level, +ensuring that database sessions are reused throughout a single request rather than creating +multiple connections. +""" + +import logging +from contextlib import contextmanager +from flask import g, current_app +from materializationengine.database import dynamic_annotation_cache + +logger = logging.getLogger(__name__) + + +@contextmanager +def request_db_session(aligned_volume_name: str): + """ + Context manager for request-scoped database sessions. + + This ensures that a single database session is reused throughout + a Flask request, reducing connection overhead and improving performance. + + Args: + aligned_volume_name (str): The name of the aligned volume/database + + Yields: + Database session that can be used for queries + + Example: + with request_db_session(aligned_volume_name) as db: + metadata = db.database.get_table_metadata(table_name) + # Additional operations use the same connection + """ + # Initialize request-level session storage if not exists + if not hasattr(g, 'db_sessions'): + g.db_sessions = {} + + # Get or create session for this aligned volume + if aligned_volume_name not in g.db_sessions: + try: + db = dynamic_annotation_cache.get_db(aligned_volume_name) + g.db_sessions[aligned_volume_name] = db + logger.debug(f"Created new request-scoped session for {aligned_volume_name}") + except Exception as e: + logger.error(f"Failed to create database session for {aligned_volume_name}: {e}") + raise + + yield g.db_sessions[aligned_volume_name] + + +def cleanup_request_db_sessions(): + """ + Clean up request-scoped database sessions. + + This should be called at the end of each request to properly + close any database sessions that were created during the request. + """ + if hasattr(g, 'db_sessions'): + for volume_name, db in g.db_sessions.items(): + try: + # Close the cached session if it exists + if hasattr(db.database, '_cached_session') and db.database._cached_session: + db.database.close_session() + logger.debug(f"Cleaned up session for {volume_name}") + except Exception as e: + logger.warning(f"Error cleaning up session for {volume_name}: {e}") + + # Clear the session storage + g.db_sessions = {} + + +def init_request_db_cleanup(app): + """ + Initialize request-scoped database session cleanup for a Flask app. + + Args: + app: Flask application instance + """ + @app.teardown_appcontext + def teardown_db_sessions(error): + """Clean up database sessions at the end of each request.""" + cleanup_request_db_sessions() + if error: + logger.error(f"Request ended with error: {error}") \ No newline at end of file diff --git a/materializationengine/shared_tasks.py b/materializationengine/shared_tasks.py index ff935e9d..49ff88d4 100644 --- a/materializationengine/shared_tasks.py +++ b/materializationengine/shared_tasks.py @@ -335,7 +335,6 @@ def update_metadata(self, mat_metadata: dict): """Update 'last_updated' column in the segmentation metadata table for a given segmentation table. - Args: mat_metadata (dict): materialization metadata @@ -346,24 +345,46 @@ def update_metadata(self, mat_metadata: dict): segmentation_table_name = mat_metadata["segmentation_table_name"] with db_manager.session_scope(aligned_volume) as session: - materialization_time_stamp = mat_metadata["materialization_time_stamp"] - try: - last_updated_time_stamp = datetime.datetime.strptime( - materialization_time_stamp, "%Y-%m-%d %H:%M:%S.%f" - ) - except ValueError: - last_updated_time_stamp = datetime.datetime.strptime( - materialization_time_stamp, "%Y-%m-%dT%H:%M:%S.%f" - ) - + + # Parse timestamp with multiple format support + timestamp_formats = [ + "%Y-%m-%d %H:%M:%S.%f", + "%Y-%m-%dT%H:%M:%S.%f", + "%Y-%m-%d %H:%M:%S.%f%z", # with timezone + "%Y-%m-%dT%H:%M:%S.%f%z", # ISO format with timezone + "%Y-%m-%d %H:%M:%S", # without microseconds + "%Y-%m-%dT%H:%M:%S", # ISO format without microseconds + ] + + last_updated_time_stamp = None + for fmt in timestamp_formats: + try: + last_updated_time_stamp = datetime.datetime.strptime( + materialization_time_stamp, fmt + ) + break + except ValueError: + continue + + if last_updated_time_stamp is None: + celery_logger.error(f"Failed to parse timestamp: {materialization_time_stamp}") + raise ValueError(f"Unsupported timestamp format: {materialization_time_stamp}") + # Query for segmentation metadata with error handling + try: seg_metadata = ( session.query(SegmentationMetadata) .filter(SegmentationMetadata.table_name == segmentation_table_name) .one() ) seg_metadata.last_updated = last_updated_time_stamp + session.commit() + celery_logger.info(f"Updated metadata for {segmentation_table_name}") + + except Exception as e: + celery_logger.error(f"Failed to update segmentation metadata for {segmentation_table_name}: {e}") + raise return { f"Table: {segmentation_table_name}": f"Time stamp {materialization_time_stamp}" diff --git a/materializationengine/utils.py b/materializationengine/utils.py index 6ba97826..c2e448a6 100644 --- a/materializationengine/utils.py +++ b/materializationengine/utils.py @@ -4,7 +4,8 @@ from flask import current_app, abort, g from middle_auth_client.decorators import users_share_common_group from celery.utils.log import get_task_logger - +from typing import Any +from cachetools import TTLCache, cached, LRUCache celery_logger = get_task_logger(__name__) @@ -105,13 +106,13 @@ def create_annotation_model( return AnnotationModel -def get_config_param(config_param: str): +def get_config_param(config_param: str, default: Any = None): try: return current_app.config[config_param] - except Exception: - return os.environ[config_param] - + except (KeyError, LookupError, RuntimeError): + return os.environ.get(config_param, default) +@cached(cache=TTLCache(maxsize=100, ttl=600)) def check_write_permission(db, table_name): metadata = db.database.get_table_metadata(table_name) if metadata["user_id"] != str(g.auth_user["id"]): @@ -125,7 +126,7 @@ def check_write_permission(db, table_name): abort(401, "Unauthorized: The table can only be written to by owner") return metadata - +@cached(cache=TTLCache(maxsize=100, ttl=600)) def check_read_permission(db, table_name): metadata = db.database.get_table_metadata(table_name) if metadata["read_permission"] == "GROUP": diff --git a/materializationengine/views.py b/materializationengine/views.py index eca7fe18..0152f416 100644 --- a/materializationengine/views.py +++ b/materializationengine/views.py @@ -41,6 +41,7 @@ from materializationengine.blueprints.client.query import specific_query from materializationengine.database import db_manager, dynamic_annotation_cache from materializationengine.blueprints.client.query_manager import QueryManager +from materializationengine.request_db import request_db_session from materializationengine.blueprints.client.datastack import validate_datastack from materializationengine.info_client import ( @@ -57,7 +58,7 @@ from materializationengine.utils import check_read_permission, get_config_param -__version__ = "5.0.1" +__version__ = "5.11.0" views_bp = Blueprint("views", __name__, url_prefix="/materialize/views") @@ -266,10 +267,53 @@ def version_error(datastack_name: str, id: int): mat_version=version.version, ) +def make_precomputed_annotation_link(datastack_name, table_name, client): + auth_disabled = get_config_param("AUTH_DISABLED", False) + auth_prefix = "" if auth_disabled else "middleauth+" + + seg_layer = client.info.segmentation_source(format_for="neuroglancer") + seg_layer = seg_layer.replace("graphene://https://", "graphene://middleauth+https://") + annotation_url = url_for( + "api.Materialization Client2_live_table_precomputed_info", + datastack_name=datastack_name, + table_name=table_name, + _external=True) + annotation_source = f"precomputed://{auth_prefix}{annotation_url}" + annotation_source = annotation_source[:-5] + seg_layer = nglui.statebuilder.SegmentationLayerConfig( + source=seg_layer, name="seg" + ) + img_layer = nglui.statebuilder.ImageLayerConfig( + source=client.info.image_source(), name="img" + ) + + sb = nglui.statebuilder.StateBuilder([img_layer, seg_layer], client=client) + json = sb.render_state( + None, + return_as="dict", + url_prefix="https://spelunker.cave-explorer.org", + target_site="mainline", + ) + json['layers'].append({ + 'type': 'annotation', + 'source': annotation_source, + 'annotationColor': '#ffffff', + 'name': table_name + }) + + sb=nglui.statebuilder.StateBuilder(base_state=json) + + url = sb.render_state(url_prefix='https://spelunker.cave-explorer.org', return_as="url", target_site="mainline") + return url + def make_seg_prop_ng_link(datastack_name, table_name, version, client, is_view=False): + auth_disabled = get_config_param("AUTH_DISABLED", False) + auth_prefix = "" if auth_disabled else "middleauth+" + seg_layer = client.info.segmentation_source(format_for="neuroglancer") - seg_layer.replace("graphene://https://", "graphene://middleauth+https://") + seg_layer = seg_layer.replace("graphene://https://", "graphene://middleauth+https://") + if is_view: seginfo_url = url_for( "api.Materialization Client2_mat_view_segment_info", @@ -287,9 +331,7 @@ def make_seg_prop_ng_link(datastack_name, table_name, version, client, is_view=F _external=True, ) - seg_info_source = f"precomputed://middleauth+{seginfo_url}".format( - seginfo_url=seginfo_url - ) + seg_info_source = f"precomputed://{auth_prefix}{seginfo_url}" # strip off the /info seg_info_source = seg_info_source[:-5] @@ -316,89 +358,52 @@ def make_seg_prop_ng_link(datastack_name, table_name, version, client, is_view=F def version_view( datastack_name: str, version: int, target_datastack=None, target_version=None ): + """View for displaying analysis tables and views for a specific datastack version.""" aligned_volume_name, pcg_table_name = get_relevant_datastack_info(datastack_name) with db_manager.session_scope(aligned_volume_name) as session: - analysis_version = ( - session.query(AnalysisVersion) - .filter(AnalysisVersion.version == target_version) - .filter(AnalysisVersion.datastack == target_datastack) - .first() - ) - - table_query = session.query(AnalysisTable).filter( - AnalysisTable.analysisversion == analysis_version - ) - tables = table_query.all() - - schema = AnalysisTableSchema(many=True) - - df = make_df_with_links_to_id( - objects=tables, - schema=AnalysisTableSchema(many=True), - url="views.table_view", - col="id", - col_value="id", - datastack_name=target_datastack, + # Get analysis version - single query instead of duplicate + analysis_version = get_analysis_version(session, target_datastack, target_version) + + if not analysis_version: + abort(404, f"Version {target_version} not found for datastack {target_datastack}") + + # Create tables DataFrame and add links + tables_df = create_tables_dataframe(session, analysis_version, target_datastack, aligned_volume_name) + tables_df = add_table_links( + tables_df, + target_datastack, + target_version, + aligned_volume_name, + current_app.config["GLOBAL_SERVER_URL"] ) - column_order = schema.declared_fields.keys() - schema_url = "{}" - client = caveclient.CAVEclient( - datastack_name, server_address=current_app.config["GLOBAL_SERVER_URL"] - ) - df["ng_link"] = df.apply( - lambda x: f"seg prop link", - axis=1, + # Convert tables to HTML + column_order = AnalysisTableSchema().declared_fields.keys() + tables_html = dataframe_to_html(tables_df, column_order) + + # Handle materialized views in separate session + with db_manager.session_scope(f"{datastack_name}__mat{version}") as mat_session: + views_df = create_views_dataframe( + mat_session, + target_datastack, + target_version, + current_app.config["GLOBAL_SERVER_URL"] ) - df["schema"] = df.schema.map( - lambda x: schema_url.format(current_app.config["GLOBAL_SERVER_URL"], x, x) - ) - df["table_name"] = df.table_name.map( - lambda x: f"{x}" - ) - - df = df.reindex(columns=list(column_order) + ["ng_link"]) - - classes = ["table table-borderless"] - with pd.option_context("display.max_colwidth", None): - output_html = df.to_html( - escape=False, classes=classes, index=False, justify="left", border=0 - ) - - with db_manager.session_scope(f"{datastack_name}__mat{version}") as mat_session: - - views = mat_session.query(AnalysisView).all() - - views_df = make_df_with_links_to_id( - objects=views, - schema=AnalysisViewSchema(many=True), - url=None, - col=None, - col_value=None, - datastack_name=target_datastack, - ) + if len(views_df) > 0: - views_df["ng_link"] = views_df.apply( - lambda x: f"seg prop link", - axis=1, - ) - classes = ["table table-borderless"] - with pd.option_context("display.max_colwidth", None): - output_view_html = views_df.to_html( - escape=False, classes=classes, index=False, justify="left", border=0 - ) + views_html = dataframe_to_html(views_df) else: - output_view_html = "

No views in datastack

" + views_html = "

No views in datastack

" - return render_template( - "version.html", - datastack=target_datastack, - analysisversion=target_version, - table=output_html, - view_table=output_view_html, - version=__version__, - ) + return render_template( + "version.html", + datastack=target_datastack, + analysisversion=target_version, + table=tables_html, + view_table=views_html, + version=__version__, + ) @views_bp.route("/datastack//table/") @@ -410,7 +415,8 @@ def table_view(datastack_name, id: int): table = session.query(AnalysisTable).filter(AnalysisTable.id == id).first() if table is None: abort(404, "table not found") - db = dynamic_annotation_cache.get_db(aligned_volume_name) + + with request_db_session(aligned_volume_name) as db: check_read_permission(db, table.table_name) # mapping = { @@ -449,11 +455,11 @@ def cell_type_local_report(datastack_name, id): analysis_datastack = table.analysisversion.datastack # Get database interface and check permissions - db = dynamic_annotation_cache.get_db(aligned_volume_name) - check_read_permission(db, table_name) - - # Create model - Model, anno_metadata = make_flat_model(db, table) + with request_db_session(aligned_volume_name) as db: + check_read_permission(db, table_name) + + # Create model + Model, anno_metadata = make_flat_model(db, table) # Second session scope for materialized data mat_db_name = f"{datastack_name}__mat{analysis_version}" @@ -639,10 +645,10 @@ def generic_report(datastack_name, id): schema = table.schema is_merged = table.analysisversion.is_merged - db = dynamic_annotation_cache.get_db(aligned_volume_name) - check_read_permission(db, table_name) - - anno_metadata = db.database.get_table_metadata(table_name) + with request_db_session(aligned_volume_name) as db: + check_read_permission(db, table_name) + + anno_metadata = db.database.get_table_metadata(table_name) mat_db_name = f"{datastack_name}__mat{version}" with db_manager.session_scope(mat_db_name) as matsession: @@ -732,4 +738,113 @@ def generic_report(datastack_name, id): n_annotations=n_annotations, anno_metadata=anno_metadata, table=output_html, - ) \ No newline at end of file + ) + +# Constants for version_view +TABLE_CSS_CLASSES = ["table", "table-borderless"] +SCHEMA_URL_TEMPLATE = "{}" + + +def get_analysis_version(session, datastack, version): + """Get analysis version for a datastack and version number.""" + analysis_version = ( + session.query(AnalysisVersion) + .filter(AnalysisVersion.version == version) + .filter(AnalysisVersion.datastack == datastack) + .first() + ) + return analysis_version + + +def create_tables_dataframe(session, analysis_version, target_datastack, aligned_volume_name): + """Create DataFrame for analysis tables with links.""" + table_query = session.query(AnalysisTable).filter( + AnalysisTable.analysisversion == analysis_version + ) + tables = table_query.all() + + if not tables: + return pd.DataFrame() + + df = make_df_with_links_to_id( + objects=tables, + schema=AnalysisTableSchema(many=True), + url="views.table_view", + col="id", + col_value="id", + datastack_name=target_datastack, + ) + + return df + + +def add_table_links(df, target_datastack, target_version, aligned_volume_name, global_server_url): + """Add ng_link and schema links to tables DataFrame.""" + if df.empty: + return df + + client = caveclient.CAVEclient( + target_datastack, server_address=global_server_url + ) + + df["ng_link"] = df.apply( + lambda x: f"seg prop link \ + annotation link", + axis=1 + ) + + df["schema"] = df.schema.map( + lambda x: SCHEMA_URL_TEMPLATE.format(global_server_url, x, x) + ) + + df["table_name"] = df.table_name.map( + lambda x: f"{x}" + ) + + return df + + +def create_views_dataframe(mat_session, target_datastack, target_version, global_server_url): + """Create DataFrame for analysis views with links.""" + views = mat_session.query(AnalysisView).all() + + if not views: + return pd.DataFrame() + + views_df = make_df_with_links_to_id( + objects=views, + schema=AnalysisViewSchema(many=True), + url=None, + col=None, + col_value=None, + datastack_name=target_datastack, + ) + + if len(views_df) > 0: + client = caveclient.CAVEclient( + target_datastack, server_address=global_server_url + ) + views_df["ng_link"] = views_df.apply( + lambda x: f"seg prop link", + axis=1, + ) + + return views_df + + +def dataframe_to_html(df, column_order=None): + """Convert DataFrame to HTML with consistent styling.""" + if df.empty: + return "

No data available

" + + if column_order: + df = df.reindex(columns=list(column_order) + (["ng_link"] if "ng_link" in df.columns else [])) + + with pd.option_context("display.max_colwidth", None): + return df.to_html( + escape=False, + classes=TABLE_CSS_CLASSES, + index=False, + justify="left", + border=0 + ) \ No newline at end of file diff --git a/materializationengine/workflows/complete_workflow.py b/materializationengine/workflows/complete_workflow.py index 29d8cdce..055f6ac8 100644 --- a/materializationengine/workflows/complete_workflow.py +++ b/materializationengine/workflows/complete_workflow.py @@ -33,7 +33,7 @@ @celery.task( bind=True, - name="workflow:run_complete_workflow", + name="orchestration:run_complete_workflow", ) def run_complete_workflow( self, diff --git a/materializationengine/workflows/ingest_new_annotations.py b/materializationengine/workflows/ingest_new_annotations.py index bfc4ec65..e45ad4ad 100644 --- a/materializationengine/workflows/ingest_new_annotations.py +++ b/materializationengine/workflows/ingest_new_annotations.py @@ -183,7 +183,7 @@ def ingest_new_annotations( if not lookup_root_ids: df = pd.DataFrame(supervoxel_data, dtype=object) drop_col_names = list(df.loc[:, df.columns.str.endswith("position")]) - df = df.drop(drop_col_names, 1) + df = df.drop(drop_col_names, axis=1) segmentation_data = df.to_dict(orient="records") else: segmentation_data = get_new_root_ids(supervoxel_data, mat_metadata) diff --git a/materializationengine/workflows/periodic_database_removal.py b/materializationengine/workflows/periodic_database_removal.py index 68cccadf..8e5af9f4 100644 --- a/materializationengine/workflows/periodic_database_removal.py +++ b/materializationengine/workflows/periodic_database_removal.py @@ -79,8 +79,27 @@ def remove_expired_databases(delete_threshold: int = 5, datastack: str = None) - .order_by(AnalysisVersion.time_stamp) .all() ) + latest_valid_version_row = ( + session.query(AnalysisVersion) + .filter(AnalysisVersion.valid == True) + .order_by(AnalysisVersion.time_stamp.desc()) + .first() + ) + + + if latest_valid_version_row: + expired_results_ids = [version.id for version in expired_results] + if latest_valid_version_row.id in expired_results_ids: + celery_logger.warning( + f"Latest valid version {latest_valid_version_row} is in expired list, " + "removing it from the expired versions." + ) + # then we want to remove the latest_valid_version from the expired list + expired_results = [ + version for version in expired_results + if version.id != latest_valid_version_row.id + ] expired_versions = [str(expired_db) for expired_db in expired_results] - non_valid_results = ( session.query(AnalysisVersion) .filter(AnalysisVersion.valid == False) @@ -127,7 +146,7 @@ def remove_expired_databases(delete_threshold: int = 5, datastack: str = None) - if len(remaining_databases) == delete_threshold: break - + if (len(databases) - len(dropped_dbs)) > delete_threshold: try: exists_query = f"SELECT 1 FROM pg_database WHERE datname='{database}'" @@ -155,6 +174,7 @@ def remove_expired_databases(delete_threshold: int = 5, datastack: str = None) - database_version = database.rsplit("__mat")[-1] expired_db = ( session.query(AnalysisVersion) + .filter(AnalysisVersion.datastack == datastack) .filter(AnalysisVersion.version == database_version) .one() ) diff --git a/materializationengine/workflows/periodic_materialization.py b/materializationengine/workflows/periodic_materialization.py index a373e762..ffc14c92 100644 --- a/materializationengine/workflows/periodic_materialization.py +++ b/materializationengine/workflows/periodic_materialization.py @@ -44,7 +44,7 @@ def process_datastack(datastack, days_to_expire, merge_tables): return True -@celery.task(name="workflow:run_periodic_materialization") +@celery.task(name="orchestration:run_periodic_materialization") def run_periodic_materialization( days_to_expire: int = None, merge_tables: bool = True, datastack: str = None ) -> None: diff --git a/materializationengine/workflows/update_database_workflow.py b/materializationengine/workflows/update_database_workflow.py index 5aa892ec..19eba84a 100644 --- a/materializationengine/workflows/update_database_workflow.py +++ b/materializationengine/workflows/update_database_workflow.py @@ -25,7 +25,7 @@ celery_logger = get_task_logger(__name__) -@celery.task(name="workflow:run_periodic_database_update") +@celery.task(name="orchestration:run_periodic_database_update") def run_periodic_database_update(datastack: str = None) -> None: """ Run update database workflow. Steps are as follows: @@ -57,7 +57,7 @@ def run_periodic_database_update(datastack: str = None) -> None: bind=True, base=LockedTask, timeout=int(60 * 60 * 24), # Task locked for 1 day - name="workflow:update_database_workflow", + name="orchestration:update_database_workflow", ) def update_database_workflow(self, datastack_info: dict, **kwargs): """Updates 'live' database: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..e35c4f97 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,138 @@ +[build-system] +requires = ["hatchling>=1.26.1"] +build-backend = "hatchling.build" + +[project] +name = "materializationengine" +version = "5.10.0" +description = "Combines DynamicAnnotationDB and PyChunkedGraph" +readme = "README.md" +requires-python = "==3.12.*" +dependencies = [ + "caveclient>=7.7.0", + "cloud-volume>=8.5.4", + "pillow>=8.3.2", + "psutil>=5.6.6", + "cloud-files>=4.6.1", + "pandas", + "flask>=2.0.2,<3.0", + "SQLAlchemy<1.4", + "flask-sqlalchemy", + "jsonschema", + "multiwrapper", + "requests>=2.26.0", + "middle-auth-client>=3.19.0", + "marshmallow-sqlalchemy", + "flask-marshmallow==0.14.0", + "Flask-Admin", + "Flask-Cors", + "flask-restx", + "flask-accepts", + "geoalchemy2>=0.9.2", + "alembic", + "celery>=5.2.3", + "cachetools", + "gevent", + "gcsfs>=0.8.0", + "pyarrow", + "flask_cors", + "numpy>=1.20", + "emannotationschemas>=5.24.12", + "dynamicannotationdb>=5.13.1", + "nglui>=3.2.1,<4", + "Flask-Limiter[redis]", + "cryptography>=44.0.2", + "uwsgi>=2.0.30", +] +authors = [ + { name = "Forrest Collman", email = "forrestc@alleninstitute.org" }, + { name = "Derrick Brittain", email = "derrickb@alleninstitute.org" } +] + +classifiers = [ + "License :: OSI Approved :: MIT License", +] + +[dependency-groups] +dev = [ + "pytest", + "pytest-cov", + "pytest-env", + "ipykernel", + "bump-my-version", + "docker", + "pyOpenSSL", + ] +docs = [ + "mkdocs", + "mkdocs-material", + "mkdocstrings[python]", +] +lint = [ + "ruff" +] +profile = [ + "scalene", + "pyinstrument", +] + +[tool.uv] +default-groups = ["dev", "docs", "lint", "profile"] + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.bumpversion] +current_version = "5.10.0" +parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" +serialize = ["{major}.{minor}.{patch}"] +search = "{current_version}" +replace = "{new_version}" +regex = false +ignore_missing_version = false +tag = true +sign_tags = false +tag_name = "v{new_version}" +tag_message = "Bump version: {current_version} → {new_version}" +allow_dirty = false +commit = true +message = "v{new_version}" +commit_args = "" +pre_commit_hooks = ['uv sync', 'git add uv.lock'] +post_commit_hooks = ["./.bmv-post-commit.sh"] + +[[tool.bumpversion.files]] +filename = "materializationengine/__init__.py" + +[[tool.bumpversion.files]] +filename = "pyproject.toml" + +[tool.ruff] +extend-exclude = ["*.ipynb"] + +[tool.ruff.lint] +select=["E9","F63","F7","F82"] + +[tool.poe.tasks.drybump] +cmd = "uv run bump-my-version bump --dry-run --verbose" +help = "Dry run of version bump for the project. Use with 'patch', 'minor', or 'major' to specify the version change." + +[tool.poe.tasks.bump] +cmd = "uv run bump-my-version bump" +help = "Bump the version number in the project. Use with 'patch', 'minor', or 'major' to specify the version change." + +[tool.poe.tasks.test] +cmd = "uv run pytest --cov=materializationengine tests" +help = "Run pytest with code coverage." + +[tool.poe.tasks.doc-preview] +cmd = "uv run mkdocs serve" +help = "Preview documentation build locally" + +[tool.poe.tasks.profile-all] +cmd = "uv run scalene" +help = "Profile cpu and memory of task with scalene" + +[tool.poe.tasks.profile] +cmd = "uv run pyinstrument -r html" +help = "Profile cpu of task with pyinstrument" diff --git a/requirements.in b/requirements.in deleted file mode 100644 index 57a90f54..00000000 --- a/requirements.in +++ /dev/null @@ -1,34 +0,0 @@ -caveclient>=5.1.0 -cloud-volume>8.5.4 -pillow>=8.3.2 -psutil>=5.6.6 -cloud-files>=4.6.1 -pandas -flask<2.3.0 -SQLAlchemy<1.4 -Flask-SQLAlchemy -jsonschema -multiwrapper -requests>=2.26.0 -middle-auth-client>=3.11.0 -flask-marshmallow==0.14.0 -marshmallow-sqlalchemy -Flask-Admin -flask-restx -flask-accepts -geoalchemy2>0.9.2 -alembic -celery>5.2.3 -cachetools -gevent -gcsfs>=0.8.0 -pyarrow -flask_cors -numpy>=1.20 -emannotationschemas>=5.17.0 -dynamicannotationdb>=5.12.0 -nglui>=3.2.1 -Flask-Limiter[redis] -Flask-Session -Werkzeug==3.0.6 - diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index ec7b67a5..00000000 --- a/requirements.txt +++ /dev/null @@ -1,560 +0,0 @@ -# This file was autogenerated by uv via the following command: -# uv pip compile requirements.in -aiohappyeyeballs==2.6.1 - # via aiohttp -aiohttp==3.11.18 - # via gcsfs -aiosignal==1.3.2 - # via aiohttp -alembic==1.14.1 - # via - # -r requirements.in - # dynamicannotationdb -amqp==5.3.1 - # via kombu -aniso8601==10.0.1 - # via flask-restx -asttokens==3.0.0 - # via stack-data -async-timeout==5.0.1 - # via - # aiohttp - # redis -atomicwrites==1.4.1 - # via neuroglancer -attrs==25.3.0 - # via - # aiohttp - # caveclient - # jsonschema - # nglui -billiard==4.2.1 - # via celery -boto3==1.38.2 - # via - # cloud-files - # cloud-volume -botocore==1.38.2 - # via - # boto3 - # s3transfer -brotli==1.1.0 - # via - # cloud-files - # urllib3 -cachelib==0.13.0 - # via flask-session -cachetools==5.5.2 - # via - # -r requirements.in - # caveclient - # google-auth - # middle-auth-client -caveclient==7.7.1 - # via - # -r requirements.in - # nglui -celery==5.5.1 - # via -r requirements.in -certifi==2025.1.31 - # via requests -chardet==5.2.0 - # via - # cloud-files - # cloud-volume -charset-normalizer==3.4.1 - # via requests -click==8.1.8 - # via - # celery - # click-didyoumean - # click-plugins - # click-repl - # cloud-files - # compressed-segmentation - # flask - # microviewer -click-didyoumean==0.3.1 - # via celery -click-plugins==1.1.1 - # via celery -click-repl==0.3.0 - # via celery -cloud-files==5.1.3 - # via - # -r requirements.in - # cloud-volume -cloud-volume==12.2.0 - # via -r requirements.in -compressed-segmentation==2.3.2 - # via cloud-volume -crc32c==2.7.1 - # via cloud-files -decorator==5.2.1 - # via - # gcsfs - # ipython -deflate==0.8.0 - # via cloud-files -deprecated==1.2.18 - # via limits -dill==0.4.0 - # via - # multiprocess - # pathos -dracopy==1.5.0 - # via cloud-volume -dynamicannotationdb==5.12.0 - # via -r requirements.in -emannotationschemas==5.17.0 - # via - # -r requirements.in - # dynamicannotationdb -exceptiongroup==1.2.2 - # via - # ipython - # pytest -executing==2.2.0 - # via stack-data -fasteners==0.19 - # via - # cloud-files - # google-apitools -fastremap==1.15.2 - # via - # cloud-volume - # osteoid -flask==2.2.5 - # via - # -r requirements.in - # flask-admin - # flask-cors - # flask-limiter - # flask-marshmallow - # flask-restx - # flask-session - # flask-sqlalchemy - # middle-auth-client -flask-accepts==0.18.4 - # via -r requirements.in -flask-admin==1.6.1 - # via -r requirements.in -flask-cors==3.0.10 - # via - # -r requirements.in - # emannotationschemas -flask-limiter==3.12 - # via -r requirements.in -flask-marshmallow==0.14.0 - # via -r requirements.in -flask-restx==1.3.0 - # via - # -r requirements.in - # flask-accepts -flask-session==0.8.0 - # via -r requirements.in -flask-sqlalchemy==2.5.1 - # via -r requirements.in -frozenlist==1.6.0 - # via - # aiohttp - # aiosignal -fsspec==2025.3.2 - # via gcsfs -furl==2.1.4 - # via middle-auth-client -gcsfs==2025.3.2 - # via -r requirements.in -geoalchemy2==0.11.1 - # via - # -r requirements.in - # dynamicannotationdb - # emannotationschemas -gevent==25.4.2 - # via - # -r requirements.in - # cloud-files - # cloud-volume -google-api-core==2.24.2 - # via - # google-cloud-core - # google-cloud-storage -google-apitools==0.5.32 - # via neuroglancer -google-auth==2.39.0 - # via - # cloud-files - # cloud-volume - # gcsfs - # google-api-core - # google-auth-oauthlib - # google-cloud-core - # google-cloud-storage - # neuroglancer -google-auth-oauthlib==1.2.2 - # via gcsfs -google-cloud-core==2.4.3 - # via - # cloud-files - # cloud-volume - # google-cloud-storage -google-cloud-storage==3.1.0 - # via - # cloud-files - # cloud-volume - # gcsfs -google-crc32c==1.7.1 - # via - # cloud-files - # google-cloud-storage - # google-resumable-media -google-resumable-media==2.7.2 - # via google-cloud-storage -googleapis-common-protos==1.70.0 - # via google-api-core -greenlet==3.2.1 - # via gevent -httplib2==0.22.0 - # via - # google-apitools - # oauth2client -idna==3.10 - # via - # requests - # yarl -importlib-resources==6.5.2 - # via flask-restx -inflection==0.5.1 - # via python-jsonschema-objects -iniconfig==2.1.0 - # via pytest -ipython==8.35.0 - # via - # caveclient - # nglui -itsdangerous==2.2.0 - # via flask -jedi==0.19.2 - # via ipython -jinja2==3.1.6 - # via flask -jmespath==1.0.1 - # via - # boto3 - # botocore -json5==0.12.0 - # via cloud-volume -jsonschema==3.2.0 - # via - # -r requirements.in - # caveclient - # cloud-volume - # dynamicannotationdb - # emannotationschemas - # flask-restx - # python-jsonschema-objects -kombu==5.5.3 - # via celery -limits==5.1.0 - # via flask-limiter -mako==1.3.10 - # via alembic -markdown==3.8 - # via python-jsonschema-objects -markdown-it-py==3.0.0 - # via rich -markupsafe==3.0.2 - # via - # jinja2 - # mako - # werkzeug - # wtforms -marshmallow==3.5.1 - # via - # dynamicannotationdb - # emannotationschemas - # flask-accepts - # flask-marshmallow - # marshmallow-jsonschema - # marshmallow-sqlalchemy -marshmallow-jsonschema==0.10.0 - # via emannotationschemas -marshmallow-sqlalchemy==0.28.2 - # via -r requirements.in -matplotlib-inline==0.1.7 - # via ipython -mdurl==0.1.2 - # via markdown-it-py -microviewer==1.12.0 - # via cloud-volume -middle-auth-client==3.18.1 - # via -r requirements.in -msgspec==0.19.0 - # via flask-session -multidict==6.4.3 - # via - # aiohttp - # yarl -multiprocess==0.70.18 - # via pathos -multiwrapper==0.1.1 - # via -r requirements.in -networkx==3.4.2 - # via - # caveclient - # cloud-volume - # osteoid -neuroglancer==2.40.1 - # via nglui -nglui==3.8.2 - # via -r requirements.in -numpy==1.26.4 - # via - # -r requirements.in - # caveclient - # cloud-volume - # compressed-segmentation - # emannotationschemas - # fastremap - # microviewer - # multiwrapper - # neuroglancer - # nglui - # osteoid - # pandas - # shapely - # simplejpeg -oauth2client==4.1.3 - # via google-apitools -oauthlib==3.2.2 - # via requests-oauthlib -ordered-set==4.1.0 - # via flask-limiter -orderedmultidict==1.0.1 - # via furl -orjson==3.10.16 - # via cloud-files -osteoid==0.3.1 - # via cloud-volume -packaging==25.0 - # via - # caveclient - # geoalchemy2 - # limits - # marshmallow-sqlalchemy - # pytest -pandas==2.2.3 - # via - # -r requirements.in - # caveclient - # nglui -parso==0.8.4 - # via jedi -pathos==0.3.4 - # via - # cloud-files - # cloud-volume -pexpect==4.9.0 - # via ipython -pillow==11.2.1 - # via - # -r requirements.in - # neuroglancer -pluggy==1.5.0 - # via pytest -posix-ipc==1.2.0 - # via cloud-volume -pox==0.3.6 - # via pathos -ppft==1.7.7 - # via pathos -prompt-toolkit==3.0.51 - # via - # click-repl - # ipython -propcache==0.3.1 - # via - # aiohttp - # yarl -proto-plus==1.26.1 - # via google-api-core -protobuf==6.30.2 - # via - # cloud-files - # cloud-volume - # google-api-core - # googleapis-common-protos - # proto-plus -psutil==7.0.0 - # via - # -r requirements.in - # cloud-volume -psycopg2-binary==2.9.10 - # via dynamicannotationdb -ptyprocess==0.7.0 - # via pexpect -pure-eval==0.2.3 - # via stack-data -pyarrow==19.0.1 - # via - # -r requirements.in - # caveclient -pyasn1==0.6.1 - # via - # oauth2client - # pyasn1-modules - # rsa -pyasn1-modules==0.4.2 - # via - # google-auth - # oauth2client -pygments==2.19.1 - # via - # ipython - # rich -pyparsing==3.2.3 - # via httplib2 -pyrsistent==0.20.0 - # via jsonschema -pysimdjson==6.0.2 - # via cloud-volume -pytest==8.3.5 - # via compressed-segmentation -python-box==7.3.2 - # via nglui -python-dateutil==2.9.0.post0 - # via - # botocore - # celery - # cloud-volume - # pandas -python-jsonschema-objects==0.4.6 - # via cloud-volume -pytz==2025.2 - # via - # dynamicannotationdb - # flask-restx - # pandas -redis==5.2.1 - # via limits -requests==2.32.3 - # via - # -r requirements.in - # caveclient - # cloud-files - # cloud-volume - # gcsfs - # google-api-core - # google-cloud-storage - # middle-auth-client - # neuroglancer - # nglui - # requests-oauthlib -requests-oauthlib==2.0.0 - # via google-auth-oauthlib -rich==13.9.4 - # via flask-limiter -rsa==4.9.1 - # via - # cloud-files - # google-auth - # oauth2client -s3transfer==0.12.0 - # via boto3 -setuptools==79.0.1 - # via - # jsonschema - # zope-event - # zope-interface -shapely==2.0.3 - # via - # dynamicannotationdb - # emannotationschemas -simplejpeg==1.8.2 - # via cloud-volume -six==1.17.0 - # via - # cloud-files - # cloud-volume - # flask-cors - # flask-marshmallow - # furl - # google-apitools - # jsonschema - # nglui - # oauth2client - # orderedmultidict - # python-dateutil - # python-jsonschema-objects -sqlalchemy==1.3.24 - # via - # -r requirements.in - # alembic - # dynamicannotationdb - # emannotationschemas - # flask-sqlalchemy - # geoalchemy2 - # marshmallow-sqlalchemy -stack-data==0.6.3 - # via ipython -tenacity==9.1.2 - # via - # cloud-files - # cloud-volume -tomli==2.2.1 - # via pytest -tornado==6.4.2 - # via neuroglancer -tqdm==4.67.1 - # via - # cloud-files - # cloud-volume -traitlets==5.14.3 - # via - # ipython - # matplotlib-inline -typing-extensions==4.13.2 - # via - # alembic - # ipython - # limits - # multidict - # rich -tzdata==2025.2 - # via - # kombu - # pandas -urllib3==2.4.0 - # via - # botocore - # caveclient - # cloud-files - # cloud-volume - # requests -vine==5.1.0 - # via - # amqp - # celery - # kombu -wcwidth==0.2.13 - # via prompt-toolkit -webcolors==24.11.1 - # via nglui -werkzeug==3.0.6 - # via - # -r requirements.in - # flask - # flask-accepts - # flask-restx -wrapt==1.17.2 - # via deprecated -wtforms==3.2.1 - # via flask-admin -yarl==1.20.0 - # via aiohttp -zope-event==5.0 - # via gevent -zope-interface==7.2 - # via gevent -zstandard==0.23.0 - # via cloud-files diff --git a/run.py b/run.py index 5e595d46..78e12efa 100644 --- a/run.py +++ b/run.py @@ -16,7 +16,7 @@ WSGIRequestHandler.protocol_version = "HTTP/1.1" application.run(host='0.0.0.0', - port=8000, + port=int(os.environ.get('FLASK_RUN_PORT', 8080)), debug=True, threaded=True, ssl_context='adhoc') diff --git a/setup.py b/setup.py deleted file mode 100644 index df97de70..00000000 --- a/setup.py +++ /dev/null @@ -1,64 +0,0 @@ -from setuptools import setup, find_packages -import os -import re -import codecs - -here = os.path.abspath(os.path.dirname(__file__)) - - -def read(*parts): - with codecs.open(os.path.join(here, *parts), 'r') as fp: - return fp.read() - - -def find_version(*file_paths): - version_file = read(*file_paths) - version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", - version_file, re.M) - if version_match: - return version_match.group(1) - raise RuntimeError("Unable to find version string.") - -with open("README.md", "r") as fh: - long_description = fh.read() - -with open('requirements.txt', 'r') as f: - required = f.read().splitlines() - -with open('test_requirements.txt', 'r') as f: - test_required = f.read().splitlines() - - -dependency_links = [] -del_ls = [] -for i_l in range(len(required)): - l = required[i_l] - if l.startswith("-e"): - dependency_links.append(l.split("-e ")[-1]) - del_ls.append(i_l) - - required.append(l.split("=")[-1]) - -for i_l in del_ls[::-1]: - del required[i_l] - -setup( - version=find_version("materializationengine", "__init__.py"), - name='materializationengine', - description="Combines DynamicAnnotationDB and PyChunkedGraph", - long_description=long_description, - long_description_content_type="text/markdown", - author='Forrest Collman', - author_email='forrestc@alleninstitute.org', - url='https://github.com/seung-lab/MaterializationEngine', - packages=find_packages(), - include_package_data=True, - install_requires=required, - setup_requires=['pytest-runner'], - tests_require=test_required, - classifiers=( - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: POSIX :: Linux", - ), - dependency_links=dependency_links) diff --git a/test_requirements.txt b/test_requirements.txt deleted file mode 100644 index e5194512..00000000 --- a/test_requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -docker -pytest -pytest-cov -pytest-env \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..970c73b4 --- /dev/null +++ b/uv.lock @@ -0,0 +1,3242 @@ +version = 1 +revision = 3 +requires-python = "==3.12.*" +resolution-markers = [ + "sys_platform != 'win32'", + "sys_platform == 'win32'", +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.12.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/6e/ab88e7cb2a4058bed2f7870276454f85a7c56cd6da79349eb314fc7bbcaa/aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce", size = 7819160, upload-time = "2025-06-14T15:15:41.354Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/6a/ce40e329788013cd190b1d62bbabb2b6a9673ecb6d836298635b939562ef/aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73", size = 700491, upload-time = "2025-06-14T15:14:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/28/d9/7150d5cf9163e05081f1c5c64a0cdf3c32d2f56e2ac95db2a28fe90eca69/aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347", size = 475104, upload-time = "2025-06-14T15:14:01.691Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/d42ba4aed039ce6e449b3e2db694328756c152a79804e64e3da5bc19dffc/aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f", size = 467948, upload-time = "2025-06-14T15:14:03.561Z" }, + { url = "https://files.pythonhosted.org/packages/99/3b/06f0a632775946981d7c4e5a865cddb6e8dfdbaed2f56f9ade7bb4a1039b/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6", size = 1714742, upload-time = "2025-06-14T15:14:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/92/a6/2552eebad9ec5e3581a89256276009e6a974dc0793632796af144df8b740/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5", size = 1697393, upload-time = "2025-06-14T15:14:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/d8/9f/bd08fdde114b3fec7a021381b537b21920cdd2aa29ad48c5dffd8ee314f1/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b", size = 1752486, upload-time = "2025-06-14T15:14:08.808Z" }, + { url = "https://files.pythonhosted.org/packages/f7/e1/affdea8723aec5bd0959171b5490dccd9a91fcc505c8c26c9f1dca73474d/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75", size = 1798643, upload-time = "2025-06-14T15:14:10.767Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9d/666d856cc3af3a62ae86393baa3074cc1d591a47d89dc3bf16f6eb2c8d32/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6", size = 1718082, upload-time = "2025-06-14T15:14:12.38Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ce/3c185293843d17be063dada45efd2712bb6bf6370b37104b4eda908ffdbd/aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8", size = 1633884, upload-time = "2025-06-14T15:14:14.415Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5b/f3413f4b238113be35dfd6794e65029250d4b93caa0974ca572217745bdb/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710", size = 1694943, upload-time = "2025-06-14T15:14:16.48Z" }, + { url = "https://files.pythonhosted.org/packages/82/c8/0e56e8bf12081faca85d14a6929ad5c1263c146149cd66caa7bc12255b6d/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462", size = 1716398, upload-time = "2025-06-14T15:14:18.589Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/33192b4761f7f9b2f7f4281365d925d663629cfaea093a64b658b94fc8e1/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae", size = 1657051, upload-time = "2025-06-14T15:14:20.223Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0b/26ddd91ca8f84c48452431cb4c5dd9523b13bc0c9766bda468e072ac9e29/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e", size = 1736611, upload-time = "2025-06-14T15:14:21.988Z" }, + { url = "https://files.pythonhosted.org/packages/c3/8d/e04569aae853302648e2c138a680a6a2f02e374c5b6711732b29f1e129cc/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a", size = 1764586, upload-time = "2025-06-14T15:14:23.979Z" }, + { url = "https://files.pythonhosted.org/packages/ac/98/c193c1d1198571d988454e4ed75adc21c55af247a9fda08236602921c8c8/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5", size = 1724197, upload-time = "2025-06-14T15:14:25.692Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9e/07bb8aa11eec762c6b1ff61575eeeb2657df11ab3d3abfa528d95f3e9337/aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf", size = 421771, upload-time = "2025-06-14T15:14:27.364Z" }, + { url = "https://files.pythonhosted.org/packages/52/66/3ce877e56ec0813069cdc9607cd979575859c597b6fb9b4182c6d5f31886/aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e", size = 447869, upload-time = "2025-06-14T15:14:29.05Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424, upload-time = "2024-12-13T17:10:40.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597, upload-time = "2024-12-13T17:10:38.469Z" }, +] + +[[package]] +name = "alembic" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/09/f844822e4e847a3f0bd41797f93c4674cd4d2462a3f6c459aa528cdf786e/alembic-1.14.1.tar.gz", hash = "sha256:496e888245a53adf1498fcab31713a469c65836f8de76e01399aa1c3e90dd213", size = 1918219, upload-time = "2025-01-19T23:15:30.12Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/7e/ac0991d1745f7d755fc1cd381b3990a45b404b4d008fc75e2a983516fbfe/alembic-1.14.1-py3-none-any.whl", hash = "sha256:1acdd7a3a478e208b0503cd73614d5e4c6efafa4e73518bb60e4f2846a37b1c5", size = 233565, upload-time = "2025-01-19T23:15:32.523Z" }, +] + +[[package]] +name = "amqp" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, +] + +[[package]] +name = "aniso8601" +version = "10.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/8d/52179c4e3f1978d3d9a285f98c706642522750ef343e9738286130423730/aniso8601-10.0.1.tar.gz", hash = "sha256:25488f8663dd1528ae1f54f94ac1ea51ae25b4d531539b8bc707fed184d16845", size = 47190, upload-time = "2025-04-18T17:29:42.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/75/e0e10dc7ed1408c28e03a6cb2d7a407f99320eb953f229d008a7a6d05546/aniso8601-10.0.1-py2.py3-none-any.whl", hash = "sha256:eb19717fd4e0db6de1aab06f12450ab92144246b257423fe020af5748c0cb89e", size = 52848, upload-time = "2025-04-18T17:29:41.492Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, +] + +[[package]] +name = "atomicwrites" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/c6/53da25344e3e3a9c01095a89f16dbcda021c609ddb42dd6d7c0528236fb2/atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11", size = 14227, upload-time = "2022-07-08T18:31:40.459Z" } + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "backrefs" +version = "5.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/a7/312f673df6a79003279e1f55619abbe7daebbb87c17c976ddc0345c04c7b/backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59", size = 5765857, upload-time = "2025-06-22T19:34:13.97Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/4d/798dc1f30468134906575156c089c492cf79b5a5fd373f07fe26c4d046bf/backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f", size = 380267, upload-time = "2025-06-22T19:34:05.252Z" }, + { url = "https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf", size = 392072, upload-time = "2025-06-22T19:34:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/9d/12/4f345407259dd60a0997107758ba3f221cf89a9b5a0f8ed5b961aef97253/backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa", size = 397947, upload-time = "2025-06-22T19:34:08.172Z" }, + { url = "https://files.pythonhosted.org/packages/41/ff/392bff89415399a979be4a65357a41d92729ae8580a66073d8ec8d810f98/backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60", size = 380265, upload-time = "2025-06-22T19:34:12.405Z" }, +] + +[[package]] +name = "billiard" +version = "4.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/58/1546c970afcd2a2428b1bfafecf2371d8951cc34b46701bea73f4280989e/billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f", size = 155031, upload-time = "2024-09-21T13:40:22.491Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/da/43b15f28fe5f9e027b41c539abc5469052e9d48fd75f8ff094ba2a0ae767/billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb", size = 86766, upload-time = "2024-09-21T13:40:20.188Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "boto3" +version = "1.39.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/97/553d12172c5015a6f3b457dc5a65fd75ce2566d04fb867845ca95982ceb7/boto3-1.39.2.tar.gz", hash = "sha256:edad811edacb8c99f2fe59b117d9b9d8ee3972beb6c090c8aa164ddbc506d67d", size = 111827, upload-time = "2025-07-02T19:15:37.821Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/12/f8c29050004890374a484f2a2da242ec7c61801e4939803b09fe3d9421d0/boto3-1.39.2-py3-none-any.whl", hash = "sha256:a37f202978f11f384e3fd1f0ff8938367b527845493f9509357e1732debc390f", size = 139909, upload-time = "2025-07-02T19:15:36.644Z" }, +] + +[[package]] +name = "botocore" +version = "1.39.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/8e/634a9bbb2a3378a287081aa745f9595bfd9bcba9ac5b3c25cd310fd6bcd1/botocore-1.39.2.tar.gz", hash = "sha256:474445fa8b281dd5db8fc62184f0f6e494d4f1efb96fe10312490583e67a9dd0", size = 14126842, upload-time = "2025-07-02T19:15:28.948Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/32/f457c2ffff15868fda9cd054778bb4454777a08868063d1fa360c441e885/botocore-1.39.2-py3-none-any.whl", hash = "sha256:921cab9aa6e1e4ceddcece5b55b57daae7722168e74409f1aafcc2c5589df676", size = 13785464, upload-time = "2025-07-02T19:15:23.879Z" }, +] + +[[package]] +name = "bracex" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642, upload-time = "2025-06-22T19:12:31.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" }, +] + +[[package]] +name = "brotli" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270, upload-time = "2023-09-07T14:05:41.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693, upload-time = "2024-10-18T12:32:23.824Z" }, + { url = "https://files.pythonhosted.org/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489, upload-time = "2024-10-18T12:32:25.641Z" }, + { url = "https://files.pythonhosted.org/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081, upload-time = "2023-09-07T14:03:57.967Z" }, + { url = "https://files.pythonhosted.org/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244, upload-time = "2023-09-07T14:03:59.319Z" }, + { url = "https://files.pythonhosted.org/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505, upload-time = "2023-09-07T14:04:01.327Z" }, + { url = "https://files.pythonhosted.org/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152, upload-time = "2023-09-07T14:04:03.033Z" }, + { url = "https://files.pythonhosted.org/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252, upload-time = "2023-09-07T14:04:04.675Z" }, + { url = "https://files.pythonhosted.org/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955, upload-time = "2023-09-07T14:04:06.585Z" }, + { url = "https://files.pythonhosted.org/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304, upload-time = "2023-09-07T14:04:08.668Z" }, + { url = "https://files.pythonhosted.org/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452, upload-time = "2023-09-07T14:04:10.736Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751, upload-time = "2023-09-07T14:04:12.875Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757, upload-time = "2023-09-07T14:04:14.551Z" }, + { url = "https://files.pythonhosted.org/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146, upload-time = "2024-10-18T12:32:27.257Z" }, + { url = "https://files.pythonhosted.org/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055, upload-time = "2024-10-18T12:32:29.376Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102, upload-time = "2024-10-18T12:32:31.371Z" }, + { url = "https://files.pythonhosted.org/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029, upload-time = "2024-10-18T12:32:33.293Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276, upload-time = "2023-09-07T14:04:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255, upload-time = "2023-09-07T14:04:17.83Z" }, +] + +[[package]] +name = "brotlicffi" +version = "1.1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/9d/70caa61192f570fcf0352766331b735afa931b4c6bc9a348a0925cc13288/brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13", size = 465192, upload-time = "2023-09-14T14:22:40.707Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/11/7b96009d3dcc2c931e828ce1e157f03824a69fb728d06bfd7b2fc6f93718/brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851", size = 453786, upload-time = "2023-09-14T14:21:57.72Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e6/a8f46f4a4ee7856fbd6ac0c6fb0dc65ed181ba46cd77875b8d9bbe494d9e/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b", size = 2911165, upload-time = "2023-09-14T14:21:59.613Z" }, + { url = "https://files.pythonhosted.org/packages/be/20/201559dff14e83ba345a5ec03335607e47467b6633c210607e693aefac40/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814", size = 2927895, upload-time = "2023-09-14T14:22:01.22Z" }, + { url = "https://files.pythonhosted.org/packages/cd/15/695b1409264143be3c933f708a3f81d53c4a1e1ebbc06f46331decbf6563/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820", size = 2851834, upload-time = "2023-09-14T14:22:03.571Z" }, + { url = "https://files.pythonhosted.org/packages/b4/40/b961a702463b6005baf952794c2e9e0099bde657d0d7e007f923883b907f/brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb", size = 341731, upload-time = "2023-09-14T14:22:05.74Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/5408a03c041114ceab628ce21766a4ea882aa6f6f0a800e04ee3a30ec6b9/brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613", size = 366783, upload-time = "2023-09-14T14:22:07.096Z" }, +] + +[[package]] +name = "bump-my-version" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "questionary" }, + { name = "rich" }, + { name = "rich-click" }, + { name = "tomlkit" }, + { name = "wcmatch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/b6/c45043a404e0878e3abeff1c25c87df78777c33760e7459901e0504f003a/bump_my_version-1.2.0.tar.gz", hash = "sha256:5120d798aaf26468a37ca0f127992dc036688b8e5e106adc8870b13c2a2df22d", size = 1136170, upload-time = "2025-06-07T14:36:12.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/e4/715484178fa80279cd67fd06c6641a4611c5ea580acdc221ad715b39d85c/bump_my_version-1.2.0-py3-none-any.whl", hash = "sha256:201e6b103ff0f2b240c9d0a6eb83db382840b1f78eb78f6d77726bed39a326d8", size = 59560, upload-time = "2025-06-07T14:36:10.232Z" }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, +] + +[[package]] +name = "caveclient" +version = "7.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cachetools" }, + { name = "ipython" }, + { name = "jsonschema" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/e2/ac88558c766690d86557f2190cb0060c517bcaa3587207d5e3dacb27ede2/caveclient-7.10.0.tar.gz", hash = "sha256:b6bc9100547ca572cb1a2ab9b2667e41765a68079d62051668c3702acbd4a744", size = 3227259, upload-time = "2025-06-25T02:15:26.658Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/2f/b55d1b62eba60e9e161dbdf8387b8869d0ceb120719e5eacdf1ab66b23d6/caveclient-7.10.0-py3-none-any.whl", hash = "sha256:06f5890f9c7595b276e4dc79587c6d103d672dba404b3b2f29d5a634c368a1cd", size = 88151, upload-time = "2025-06-25T02:15:24.87Z" }, +] + +[[package]] +name = "celery" +version = "5.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "billiard" }, + { name = "click" }, + { name = "click-didyoumean" }, + { name = "click-plugins" }, + { name = "click-repl" }, + { name = "kombu" }, + { name = "python-dateutil" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/7d/6c289f407d219ba36d8b384b42489ebdd0c84ce9c413875a8aae0c85f35b/celery-5.5.3.tar.gz", hash = "sha256:6c972ae7968c2b5281227f01c3a3f984037d21c5129d07bf3550cc2afc6b10a5", size = 1667144, upload-time = "2025-06-01T11:08:12.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/af/0dcccc7fdcdf170f9a1585e5e96b6fb0ba1749ef6be8c89a6202284759bd/celery-5.5.3-py3-none-any.whl", hash = "sha256:0b5761a07057acee94694464ca482416b959568904c9dfa41ce8413a7d65d525", size = 438775, upload-time = "2025-06-01T11:08:09.94Z" }, +] + +[[package]] +name = "certifi" +version = "2025.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "click-didyoumean" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089, upload-time = "2024-03-24T08:22:07.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631, upload-time = "2024-03-24T08:22:06.356Z" }, +] + +[[package]] +name = "click-plugins" +version = "1.1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" }, +] + +[[package]] +name = "click-repl" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, +] + +[[package]] +name = "cloud-files" +version = "5.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, + { name = "brotli" }, + { name = "chardet" }, + { name = "click" }, + { name = "crc32c" }, + { name = "deflate" }, + { name = "fasteners" }, + { name = "gevent" }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-cloud-storage" }, + { name = "google-crc32c" }, + { name = "orjson" }, + { name = "pathos" }, + { name = "protobuf" }, + { name = "requests" }, + { name = "rsa" }, + { name = "six" }, + { name = "tenacity" }, + { name = "tqdm" }, + { name = "urllib3" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/8f/bfb96451f8b8ba258d524ac76ac88ae7e9f96a4fb3ba3349f8978117e9dc/cloud_files-5.4.1.tar.gz", hash = "sha256:c4eef192beac854e8a5c45c1a36b71101c46524927eb4b96200b4ef60ac4c6eb", size = 86610, upload-time = "2025-06-30T19:57:19.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/10/3dbe88e098ec9fb81d6a2856842a99eca5bff8c9d31889552853e6354ef9/cloud_files-5.4.1-py3-none-any.whl", hash = "sha256:1f82861bf0b223516aa403da9a59c14f7121e851f506e889e150df0a9bd4766b", size = 67078, upload-time = "2025-06-30T19:57:17.96Z" }, +] + +[[package]] +name = "cloud-volume" +version = "12.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, + { name = "chardet" }, + { name = "cloud-files" }, + { name = "compressed-segmentation" }, + { name = "dracopy" }, + { name = "fastremap" }, + { name = "gevent" }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-cloud-storage" }, + { name = "json5" }, + { name = "jsonschema" }, + { name = "microviewer" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "osteoid" }, + { name = "pathos" }, + { name = "posix-ipc", marker = "sys_platform != 'win32'" }, + { name = "protobuf" }, + { name = "psutil" }, + { name = "pysimdjson" }, + { name = "python-dateutil" }, + { name = "python-jsonschema-objects" }, + { name = "requests" }, + { name = "simplejpeg" }, + { name = "six" }, + { name = "tenacity" }, + { name = "tqdm" }, + { name = "urllib3", extra = ["brotli"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/8a/f61fc57dadedb38aeae5e40e3c180a82750aaa97c9902812e3003a3dc306/cloud_volume-12.3.1.tar.gz", hash = "sha256:a94c4010226989a4100868e6b9e74922c094303102e640b44b7b1204a94528dd", size = 254471, upload-time = "2025-06-04T23:45:06.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/9f/ab398aaeccf828bdec0eff7561e4e9523032b71cfc8ce3a966edac465ae4/cloud_volume-12.3.1-py3-none-any.whl", hash = "sha256:ccd8ab38d7c7c10521b62ff3e90e79641a00a599846aff75c6e0d7e17562719f", size = 222066, upload-time = "2025-06-04T23:45:03.925Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/39/069100b84d7418bc358d81669d5748efb14b9cceacd2f9c75f550424132f/cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64", size = 22113, upload-time = "2025-01-14T17:02:05.085Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e", size = 20992, upload-time = "2025-01-14T17:02:02.417Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "comm" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210, upload-time = "2024-03-12T16:53:41.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180, upload-time = "2024-03-12T16:53:39.226Z" }, +] + +[[package]] +name = "compressed-segmentation" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "numpy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/11/0bf9a93af484325bb775e2e0b6868e698dc630b529abb2b69359feb4021e/compressed_segmentation-2.3.2.tar.gz", hash = "sha256:376ae5b71e47d6edadfeb48b0b23b0b333da77473c024c48a7f92533bab5b003", size = 26671, upload-time = "2025-01-23T20:03:19.003Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/19/cd8ad01589a985fe2f6561140872bea5f74b896ec2aa98c1caf56f7f5342/compressed_segmentation-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:35dd7e7ccfdac74ace9fe2d7ccc1539d8084ed99c6603a1e373a5f2ac1d49d32", size = 169059, upload-time = "2025-01-23T20:02:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e7/95f70078e13a03fdc004a7ee11831afe6e9421ab6de166a4a2bcd9c5d83a/compressed_segmentation-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec2dd616d07b089c67390a8dd01a4d7864fccce98069961fe7a2fb775c051a8", size = 153173, upload-time = "2025-01-23T20:02:32.417Z" }, + { url = "https://files.pythonhosted.org/packages/9e/dd/922d1591398fcb905aafcc2edd13cfdc49f2da108b76124276f502c5c837/compressed_segmentation-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a3fc3952191808ed74800e8eefa5194e69f2fecf4f04eaed965c9be5ecf0d00", size = 1008068, upload-time = "2025-01-23T20:02:33.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/98/fbf4cfa6bbcbcebd2f5441a84a676d29ef7e3bb65533fdb5a27d0bfc4955/compressed_segmentation-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2d9177245bd8245b320e233fa31dfd413d25ef95c982c88f7079695534c76cd", size = 1035456, upload-time = "2025-01-23T20:02:34.749Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d4/83d72dbabcb71c6aca2c5a29abfe944c01e48a3896ee145442bec1bc7a4d/compressed_segmentation-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:434d831b1d6f2d026ac0b6767d6e791e2116c6c556037dd72ac71eaffc57c304", size = 2055834, upload-time = "2025-01-23T20:02:36.834Z" }, + { url = "https://files.pythonhosted.org/packages/67/f3/426cb68a34fe971988e9a22097a6d432e59c34c120a2116b3cc8c149dd49/compressed_segmentation-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:49aceaa41e691c062c3b706868dfdb39f2984f3374fbc90dc09b20c0f2392dfb", size = 136615, upload-time = "2025-01-23T20:02:38.448Z" }, +] + +[[package]] +name = "coverage" +version = "7.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/e0/98670a80884f64578f0c22cd70c5e81a6e07b08167721c7487b4d70a7ca0/coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec", size = 813650, upload-time = "2025-06-13T13:02:28.627Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/d9/7f66eb0a8f2fce222de7bdc2046ec41cb31fe33fb55a330037833fb88afc/coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626", size = 212336, upload-time = "2025-06-13T13:01:10.909Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/e07cb920ef3addf20f052ee3d54906e57407b6aeee3227a9c91eea38a665/coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb", size = 212571, upload-time = "2025-06-13T13:01:12.518Z" }, + { url = "https://files.pythonhosted.org/packages/78/f8/96f155de7e9e248ca9c8ff1a40a521d944ba48bec65352da9be2463745bf/coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300", size = 246377, upload-time = "2025-06-13T13:01:14.87Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cf/1d783bd05b7bca5c10ded5f946068909372e94615a4416afadfe3f63492d/coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8", size = 243394, upload-time = "2025-06-13T13:01:16.23Z" }, + { url = "https://files.pythonhosted.org/packages/02/dd/e7b20afd35b0a1abea09fb3998e1abc9f9bd953bee548f235aebd2b11401/coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5", size = 245586, upload-time = "2025-06-13T13:01:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/4e/38/b30b0006fea9d617d1cb8e43b1bc9a96af11eff42b87eb8c716cf4d37469/coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd", size = 245396, upload-time = "2025-06-13T13:01:19.164Z" }, + { url = "https://files.pythonhosted.org/packages/31/e4/4d8ec1dc826e16791f3daf1b50943e8e7e1eb70e8efa7abb03936ff48418/coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898", size = 243577, upload-time = "2025-06-13T13:01:22.433Z" }, + { url = "https://files.pythonhosted.org/packages/25/f4/b0e96c5c38e6e40ef465c4bc7f138863e2909c00e54a331da335faf0d81a/coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d", size = 244809, upload-time = "2025-06-13T13:01:24.143Z" }, + { url = "https://files.pythonhosted.org/packages/8a/65/27e0a1fa5e2e5079bdca4521be2f5dabf516f94e29a0defed35ac2382eb2/coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74", size = 214724, upload-time = "2025-06-13T13:01:25.435Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a8/d5b128633fd1a5e0401a4160d02fa15986209a9e47717174f99dc2f7166d/coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e", size = 215535, upload-time = "2025-06-13T13:01:27.861Z" }, + { url = "https://files.pythonhosted.org/packages/a3/37/84bba9d2afabc3611f3e4325ee2c6a47cd449b580d4a606b240ce5a6f9bf/coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342", size = 213904, upload-time = "2025-06-13T13:01:29.202Z" }, + { url = "https://files.pythonhosted.org/packages/08/b8/7ddd1e8ba9701dea08ce22029917140e6f66a859427406579fd8d0ca7274/coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c", size = 204000, upload-time = "2025-06-13T13:02:27.173Z" }, +] + +[[package]] +name = "crc32c" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/4c/4e40cc26347ac8254d3f25b9f94710b8e8df24ee4dddc1ba41907a88a94d/crc32c-2.7.1.tar.gz", hash = "sha256:f91b144a21eef834d64178e01982bb9179c354b3e9e5f4c803b0e5096384968c", size = 45712, upload-time = "2024-09-24T06:20:17.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/02/998dc21333413ce63fe4c1ca70eafe61ca26afc7eb353f20cecdb77d614e/crc32c-2.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f7d1c4e761fe42bf856130daf8b2658df33fe0ced3c43dadafdfeaa42b57b950", size = 49568, upload-time = "2024-09-24T06:18:32.425Z" }, + { url = "https://files.pythonhosted.org/packages/9c/3e/e3656bfa76e50ef87b7136fef2dbf3c46e225629432fc9184fdd7fd187ff/crc32c-2.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:73361c79a6e4605204457f19fda18b042a94508a52e53d10a4239da5fb0f6a34", size = 37019, upload-time = "2024-09-24T06:18:34.097Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7d/5ff9904046ad15a08772515db19df43107bf5e3901a89c36a577b5f40ba0/crc32c-2.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd778fc8ac0ed2ffbfb122a9aa6a0e409a8019b894a1799cda12c01534493e0", size = 35373, upload-time = "2024-09-24T06:18:35.02Z" }, + { url = "https://files.pythonhosted.org/packages/4d/41/4aedc961893f26858ab89fc772d0eaba91f9870f19eaa933999dcacb94ec/crc32c-2.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ef661b34e9f25991fface7f9ad85e81bbc1b3fe3b916fd58c893eabe2fa0b8", size = 54675, upload-time = "2024-09-24T06:18:35.954Z" }, + { url = "https://files.pythonhosted.org/packages/d6/63/8cabf09b7e39b9fec8f7010646c8b33057fc8d67e6093b3cc15563d23533/crc32c-2.7.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:571aa4429444b5d7f588e4377663592145d2d25eb1635abb530f1281794fc7c9", size = 52386, upload-time = "2024-09-24T06:18:36.896Z" }, + { url = "https://files.pythonhosted.org/packages/79/13/13576941bf7cf95026abae43d8427c812c0054408212bf8ed490eda846b0/crc32c-2.7.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c02a3bd67dea95cdb25844aaf44ca2e1b0c1fd70b287ad08c874a95ef4bb38db", size = 53495, upload-time = "2024-09-24T06:18:38.099Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b6/55ffb26d0517d2d6c6f430ce2ad36ae7647c995c5bfd7abce7f32bb2bad1/crc32c-2.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d17637c4867672cb8adeea007294e3c3df9d43964369516cfe2c1f47ce500a", size = 54456, upload-time = "2024-09-24T06:18:39.051Z" }, + { url = "https://files.pythonhosted.org/packages/c2/1a/5562e54cb629ecc5543d3604dba86ddfc7c7b7bf31d64005b38a00d31d31/crc32c-2.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4a400ac3c69a32e180d8753fd7ec7bccb80ade7ab0812855dce8a208e72495f", size = 52647, upload-time = "2024-09-24T06:18:40.021Z" }, + { url = "https://files.pythonhosted.org/packages/48/ec/ce4138eaf356cd9aae60bbe931755e5e0151b3eca5f491fce6c01b97fd59/crc32c-2.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:588587772e55624dd9c7a906ec9e8773ae0b6ac5e270fc0bc84ee2758eba90d5", size = 53332, upload-time = "2024-09-24T06:18:40.925Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b5/144b42cd838a901175a916078781cb2c3c9f977151c9ba085aebd6d15b22/crc32c-2.7.1-cp312-cp312-win32.whl", hash = "sha256:9f14b60e5a14206e8173dd617fa0c4df35e098a305594082f930dae5488da428", size = 38371, upload-time = "2024-09-24T06:18:42.711Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c4/7929dcd5d9b57db0cce4fe6f6c191049380fc6d8c9b9f5581967f4ec018e/crc32c-2.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:7c810a246660a24dc818047dc5f89c7ce7b2814e1e08a8e99993f4103f7219e8", size = 39805, upload-time = "2024-09-24T06:18:43.6Z" }, +] + +[[package]] +name = "cryptography" +version = "45.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903, upload-time = "2025-07-02T13:06:25.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/fb/09e28bc0c46d2c547085e60897fea96310574c70fb21cd58a730a45f3403/cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8", size = 7043092, upload-time = "2025-07-02T13:05:01.514Z" }, + { url = "https://files.pythonhosted.org/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926, upload-time = "2025-07-02T13:05:04.741Z" }, + { url = "https://files.pythonhosted.org/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235, upload-time = "2025-07-02T13:05:07.084Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785, upload-time = "2025-07-02T13:05:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050, upload-time = "2025-07-02T13:05:11.069Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379, upload-time = "2025-07-02T13:05:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355, upload-time = "2025-07-02T13:05:15.017Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087, upload-time = "2025-07-02T13:05:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873, upload-time = "2025-07-02T13:05:18.743Z" }, + { url = "https://files.pythonhosted.org/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651, upload-time = "2025-07-02T13:05:21.382Z" }, + { url = "https://files.pythonhosted.org/packages/2e/92/cc723dd6d71e9747a887b94eb3827825c6c24b9e6ce2bb33b847d31d5eaa/cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9", size = 2929050, upload-time = "2025-07-02T13:05:23.39Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/197da38a5911a48dd5389c043de4aec4b3c94cb836299b01253940788d78/cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63", size = 3403224, upload-time = "2025-07-02T13:05:25.202Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2b/160ce8c2765e7a481ce57d55eba1546148583e7b6f85514472b1d151711d/cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8", size = 7017143, upload-time = "2025-07-02T13:05:27.229Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780, upload-time = "2025-07-02T13:05:29.299Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091, upload-time = "2025-07-02T13:05:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711, upload-time = "2025-07-02T13:05:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299, upload-time = "2025-07-02T13:05:34.94Z" }, + { url = "https://files.pythonhosted.org/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558, upload-time = "2025-07-02T13:05:37.288Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020, upload-time = "2025-07-02T13:05:39.102Z" }, + { url = "https://files.pythonhosted.org/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759, upload-time = "2025-07-02T13:05:41.398Z" }, + { url = "https://files.pythonhosted.org/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991, upload-time = "2025-07-02T13:05:43.64Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189, upload-time = "2025-07-02T13:05:46.045Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ea/a78a0c38f4c8736287b71c2ea3799d173d5ce778c7d6e3c163a95a05ad2a/cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", size = 2911769, upload-time = "2025-07-02T13:05:48.329Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016, upload-time = "2025-07-02T13:05:50.811Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/75/087fe07d40f490a78782ff3b0a30e3968936854105487decdb33446d4b0e/debugpy-1.8.14.tar.gz", hash = "sha256:7cd287184318416850aa8b60ac90105837bb1e59531898c07569d197d2ed5322", size = 1641444, upload-time = "2025-04-10T19:46:10.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/2a/ac2df0eda4898f29c46eb6713a5148e6f8b2b389c8ec9e425a4a1d67bf07/debugpy-1.8.14-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:8899c17920d089cfa23e6005ad9f22582fd86f144b23acb9feeda59e84405b84", size = 2501268, upload-time = "2025-04-10T19:46:26.044Z" }, + { url = "https://files.pythonhosted.org/packages/10/53/0a0cb5d79dd9f7039169f8bf94a144ad3efa52cc519940b3b7dde23bcb89/debugpy-1.8.14-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6bb5c0dcf80ad5dbc7b7d6eac484e2af34bdacdf81df09b6a3e62792b722826", size = 4221077, upload-time = "2025-04-10T19:46:27.464Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d5/84e01821f362327bf4828728aa31e907a2eca7c78cd7c6ec062780d249f8/debugpy-1.8.14-cp312-cp312-win32.whl", hash = "sha256:281d44d248a0e1791ad0eafdbbd2912ff0de9eec48022a5bfbc332957487ed3f", size = 5255127, upload-time = "2025-04-10T19:46:29.467Z" }, + { url = "https://files.pythonhosted.org/packages/33/16/1ed929d812c758295cac7f9cf3dab5c73439c83d9091f2d91871e648093e/debugpy-1.8.14-cp312-cp312-win_amd64.whl", hash = "sha256:5aa56ef8538893e4502a7d79047fe39b1dae08d9ae257074c6464a7b290b806f", size = 5297249, upload-time = "2025-04-10T19:46:31.538Z" }, + { url = "https://files.pythonhosted.org/packages/97/1a/481f33c37ee3ac8040d3d51fc4c4e4e7e61cb08b8bc8971d6032acc2279f/debugpy-1.8.14-py2.py3-none-any.whl", hash = "sha256:5cd9a579d553b6cb9759a7908a41988ee6280b961f24f63336835d9418216a20", size = 5256230, upload-time = "2025-04-10T19:46:54.077Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "deflate" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/90/8a94974c83e695d9e8a2f081717bf9d1a2571a4abc9939f0e1b12c8c7de9/deflate-0.8.0.tar.gz", hash = "sha256:81788f9ba8c22d39bd8e668fc6a18cd0ba93ebf142e41cb1ccf4e9915108f331", size = 213101, upload-time = "2025-03-30T00:57:41.703Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/04/952d4bcd3157e5e313ff70d03e6091e098e945d5c54ad04b5a8df94f684a/deflate-0.8.0-cp311-abi3-macosx_10_9_x86_64.whl", hash = "sha256:56c29834192424844102c0beddcb61d56662b5dc1e7dfd32b7c8fe091d396a84", size = 55174, upload-time = "2025-03-30T00:57:05.605Z" }, + { url = "https://files.pythonhosted.org/packages/52/38/e58a7814368e1caa3860e653c65e6457904c8ee739c480368f14335b8548/deflate-0.8.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:4b6c2939ed9159e57f3058c0256e8f76842968cda7c46a89a863bc80c1b47832", size = 40545, upload-time = "2025-03-30T00:57:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/91/82/a0c4023650db451f0981ee0b5b4f1c0a00ee6948bbcf970678930349f871/deflate-0.8.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:808b123785e1fd0030dfe894e97a664bd23ee4ecc03cd5f768f32a0b53544e3d", size = 50217, upload-time = "2025-03-30T00:57:07.651Z" }, + { url = "https://files.pythonhosted.org/packages/83/c7/7e599a7bd9176c27e7b4ce547a314defe2cb458dbfcc5468c118a958c08f/deflate-0.8.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f72f2ac7045ca54f9bed611ffc196cf5a0ccaae9261ad069dce34d8b2e9c13c", size = 51734, upload-time = "2025-03-30T00:57:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/97/40/51df16784cb9b0cd515d13d023c289fbf9cf8f6842bc636cfa07d7dc19e9/deflate-0.8.0-cp311-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b87b6a5798a3e12373ef572ae40ae2b04276990317472b460694ec14943a04b0", size = 54720, upload-time = "2025-03-30T00:57:09.798Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7f/1893ba84750bdd92a21012f7192d377727bbe8cb7c318f267ac3ebe53f33/deflate-0.8.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e3938ef16d4d9b42c0d5f78643f41dc28f9b03c85f354b9c83a45a903ab0cf84", size = 51243, upload-time = "2025-03-30T00:57:10.62Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/627cbcc9acd11ea1ff5278c3c083eabab569bf41549ef575bae170d44dbf/deflate-0.8.0-cp311-abi3-musllinux_1_2_i686.whl", hash = "sha256:48f316680b18729e86866f182abdb7b38eddf42c12258ebc1c057f168f3552e8", size = 57174, upload-time = "2025-03-30T00:57:11.438Z" }, + { url = "https://files.pythonhosted.org/packages/5b/37/bab5203ca9827ecab665cd3c9fee0cb7dd295c19a181580c8689308f66a3/deflate-0.8.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d6aa7415715e16484137c5e260546aa204e2b67f36b1eb0d3212bca8100e3b45", size = 55227, upload-time = "2025-03-30T00:57:12.26Z" }, + { url = "https://files.pythonhosted.org/packages/a8/17/5fb734d37d22ce853caaae81bb8b92e9c820dee240803a0071e1cfc5d7fc/deflate-0.8.0-cp311-abi3-win32.whl", hash = "sha256:6d7ddec70173f8dcb45bcb91f38640f9d18e4474f05d90f6f62bf25a548dc911", size = 44149, upload-time = "2025-03-30T00:57:13.453Z" }, + { url = "https://files.pythonhosted.org/packages/6d/90/7767f2cfb33e1659ec71bf342d00155024aab5e85280501be7e955df724a/deflate-0.8.0-cp311-abi3-win_amd64.whl", hash = "sha256:cb6b85f6a352138d7d4542edd99503ac106315c6469ea72442352ebded304293", size = 50084, upload-time = "2025-03-30T00:57:14.216Z" }, +] + +[[package]] +name = "deprecated" +version = "1.2.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" }, +] + +[[package]] +name = "dill" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, +] + +[[package]] +name = "docker" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, +] + +[[package]] +name = "dracopy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/1b/c9ba28e5cf3c6532707705e30dfcc83cbb8b01b61a282eede0cbfd306e73/dracopy-1.5.0.tar.gz", hash = "sha256:bafb7c410995ab776cbedbe88fef687550c2c0b6e5c9aa58b0b608801bdbef9f", size = 71422500, upload-time = "2025-03-05T22:41:51.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/dd/0897d6f3b32f0644e4e6e6942e0428d6f340619f3837bc51eabb4226f3a6/dracopy-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fac218b6de5d469ddb655e5af8c7003a98346731a846a39c688bd6f3858935aa", size = 2963251, upload-time = "2025-03-05T22:41:08.649Z" }, + { url = "https://files.pythonhosted.org/packages/a2/4b/a6bc3350a44afeba23ed18d9eac82b451f8ae2dd303fc63c640166f6444c/dracopy-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f457422e6579f7ed90e0615c52018ed390d953603f231762b50c0da84dcb813d", size = 2569316, upload-time = "2025-03-05T22:41:10.124Z" }, + { url = "https://files.pythonhosted.org/packages/63/48/69e0b403b74d0e87372357048c07e9e84a7bd796b4e65a21796fd71cc5fc/dracopy-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:785c34f8b5b09d7eed8672390aa8218353a8e1d17454098706a242cf556bc699", size = 4553474, upload-time = "2025-03-05T22:41:13.529Z" }, + { url = "https://files.pythonhosted.org/packages/31/7c/a329c9347a8a8955f32d603fd593f0ecc6f5a8df4978ee14907bf72b20ee/dracopy-1.5.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d65258183834c67b0daae455e2c1461005113aa53c402e1fae228b77bbdd22", size = 4895874, upload-time = "2025-03-05T22:41:15.132Z" }, + { url = "https://files.pythonhosted.org/packages/fd/b6/e55db46e117f2f9867996598e4b07afc995e8394930afc5ee2026bb5e7ec/dracopy-1.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cfb2b932c06d7c2cac6068827e93c3f15caf8fd92868a22ef403140be8abaa4", size = 4652079, upload-time = "2025-03-05T22:41:17.526Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d4/1c100925468c9f8be5d02bc0ceb2e835796e722b9e9de7cb51274f970bc7/dracopy-1.5.0-cp312-cp312-win32.whl", hash = "sha256:c3b499fdf721de9e9c6367eecdb966af7cda6054a3386bd5d18e4aa8f7b3d4f5", size = 4241105, upload-time = "2025-03-05T22:41:19.027Z" }, + { url = "https://files.pythonhosted.org/packages/d4/37/595b627ca3f19387cc5039c9c1abf6272090f285c7b234ecf62cf956a518/dracopy-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:a22f33f1ff5c82de2ec9e297e576bf8be3b75d30c3228f2206caea1a88db0bfc", size = 4943610, upload-time = "2025-03-05T22:41:21.148Z" }, +] + +[[package]] +name = "dynamicannotationdb" +version = "5.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "emannotationschemas" }, + { name = "geoalchemy2" }, + { name = "jsonschema" }, + { name = "marshmallow" }, + { name = "psycopg2-binary" }, + { name = "pytz" }, + { name = "shapely" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/b5/476da64a201aaf4287fa9f6e66fc4b899192c741d436afdb2e8e862b625b/DynamicAnnotationDB-5.13.1.tar.gz", hash = "sha256:fd047086090ce0169f59f469789c58f8f145681052db56291d6741419325a2b4", size = 32335, upload-time = "2025-07-23T15:01:51.135Z" } + +[[package]] +name = "emannotationschemas" +version = "5.24.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask-cors" }, + { name = "geoalchemy2" }, + { name = "jsonschema" }, + { name = "marshmallow" }, + { name = "marshmallow-jsonschema" }, + { name = "numpy" }, + { name = "shapely" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/60/2ccf0c3aa7d47f2fbda5d02ba023df5d88788ec1c579d7597702fca982f6/emannotationschemas-5.24.12.tar.gz", hash = "sha256:bd36cf0db1570e5098e3701c714ca1be20bb1de777f7624a9d37f0c805d3a5d0", size = 38327, upload-time = "2025-08-29T16:59:05.613Z" } + +[[package]] +name = "executing" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693, upload-time = "2025-01-22T15:41:29.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" }, +] + +[[package]] +name = "fasteners" +version = "0.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/d4/e834d929be54bfadb1f3e3b931c38e956aaa3b235a46a3c764c26c774902/fasteners-0.19.tar.gz", hash = "sha256:b4f37c3ac52d8a445af3a66bce57b33b5e90b97c696b7b984f530cf8f0ded09c", size = 24832, upload-time = "2023-09-19T17:11:20.228Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/bf/fd60001b3abc5222d8eaa4a204cd8c0ae78e75adc688f33ce4bf25b7fafa/fasteners-0.19-py3-none-any.whl", hash = "sha256:758819cb5d94cdedf4e836988b74de396ceacb8e2794d21f82d131fd9ee77237", size = 18679, upload-time = "2023-09-19T17:11:18.725Z" }, +] + +[[package]] +name = "fastremap" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/88/907f3f0741608989bfa15c84d555d4eb1aeb24753becfd45bbb611d5ada0/fastremap-1.17.1.tar.gz", hash = "sha256:e4ef75182154597c7feb202f1c7b71b8ef8d24076f6f03df641173fef5f81c40", size = 49321, upload-time = "2025-06-24T18:54:46.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/21/ae495085e34cf92f91deaaaf1ee006f609df8c934c0bc4d3e93d38ed99fe/fastremap-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1ff68d65eb937851fff10f1d8eb8a13ec7cb84016ce752e0193e0a6214625f64", size = 752703, upload-time = "2025-06-24T18:54:12.866Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8c/55b5577dcbaaa7527be2fc41eb488861f810dd7e9aaf3cdd4b8102c044d3/fastremap-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecff9188957cb7e133f41520aee26a0b8c95418e27ab87380c04707f4b4994be", size = 622336, upload-time = "2025-06-24T18:54:13.912Z" }, + { url = "https://files.pythonhosted.org/packages/a2/95/5c779436cb38c2bd1f196f72024f7c4268e65f9d2aaf1b5dd73b4423ac99/fastremap-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79faeef8c47fb46ba65667436cf763b80fd045c9f2e9eac95d747b04bad00c90", size = 6722325, upload-time = "2025-06-24T18:54:16.722Z" }, + { url = "https://files.pythonhosted.org/packages/d1/0f/5aba90436900ccd33a1096dae05d456984df691f87f21bd96b9d65f26aaf/fastremap-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a34fcbf1300ed24d3b031aabc6e41fb1aff3bf22e9fac5eba0244dece183286", size = 6881755, upload-time = "2025-06-24T18:54:19.824Z" }, + { url = "https://files.pythonhosted.org/packages/45/2a/42f8917f11e387910ec388dd026590e747af97f05a16b49125de8986ee44/fastremap-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a48217725fcf638440cf183f4a8d320c17406cfb8822dd74fc40762bef5b0c6a", size = 452676, upload-time = "2025-06-24T18:54:23.716Z" }, + { url = "https://files.pythonhosted.org/packages/5e/49/3f79e667d26c49d5d5735b59bb9a21b60d7f706db86fedbc41dc573edf57/fastremap-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:837d9c05ca841a14d69bdb73f738e0c0686a3190c377999c8be778f39d8bed76", size = 618074, upload-time = "2025-06-24T18:54:21.259Z" }, +] + +[[package]] +name = "flask" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/4ace17e37abd9c21715dea5ee11774a25e404c486a7893fa18e764326ead/flask-2.3.3.tar.gz", hash = "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc", size = 672756, upload-time = "2023-08-21T19:52:35.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/56/26f0be8adc2b4257df20c1c4260ddd0aa396cf8e75d90ab2f7ff99bc34f9/flask-2.3.3-py3-none-any.whl", hash = "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b", size = 96112, upload-time = "2023-08-21T19:52:33.115Z" }, +] + +[[package]] +name = "flask-accepts" +version = "0.18.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask-restx" }, + { name = "marshmallow" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/e2/cb2f45589d7d6be98f537b887f3c6177fd56542be279bc9c29dfe36d00d2/flask_accepts-0.18.4.tar.gz", hash = "sha256:ac8a67ccdec0b6de623cae54c677132f476307faaaa9ead6209abf20c207460c", size = 17689, upload-time = "2021-10-05T12:52:37.827Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/af/c3ad76fd927e42a73afa8dc10b813b2f6dddfdfd60508db63134182ed502/flask_accepts-0.18.4-py3-none-any.whl", hash = "sha256:63614b33d885afb78b15ca3384582eabf94feafdc3b001002f52d6a27c82d631", size = 15981, upload-time = "2021-10-05T12:52:36.367Z" }, +] + +[[package]] +name = "flask-admin" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "wtforms" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/4d/7cad383a93e3e1dd9378f1fcf05ddc532c6d921fb30c19ce8f8583630f24/Flask-Admin-1.6.1.tar.gz", hash = "sha256:24cae2af832b6a611a01d7dc35f42d266c1d6c75a426b869d8cb241b78233369", size = 6651224, upload-time = "2023-02-20T12:55:41.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/b3/656c78dfef163517dbbc9fd106f0604e37b436ad51f9d9450b60e9407e35/Flask_Admin-1.6.1-py3-none-any.whl", hash = "sha256:fd8190f1ec3355913a22739c46ed3623f1d82b8112cde324c60a6fc9b21c9406", size = 7498141, upload-time = "2023-02-20T12:55:32.767Z" }, +] + +[[package]] +name = "flask-cors" +version = "3.0.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/25/e3b2553d22ed542be807739556c69621ad2ab276ae8d5d2560f4ed20f652/Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de", size = 30867, upload-time = "2021-01-06T00:25:42.749Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/84/901e700de86604b1c4ef4b57110d4e947c218b9997adf5d38fa7da493bce/Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438", size = 14067, upload-time = "2021-01-06T00:25:41.464Z" }, +] + +[[package]] +name = "flask-limiter" +version = "3.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "limits" }, + { name = "ordered-set" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/75/92b237dd4f6e19196bc73007fff288ab1d4c64242603f3c401ff8fc58a42/flask_limiter-3.12.tar.gz", hash = "sha256:f9e3e3d0c4acd0d1ffbfa729e17198dd1042f4d23c130ae160044fc930e21300", size = 303162, upload-time = "2025-03-15T02:23:10.734Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/ba/40dafa278ee6a4300179d2bf59a1aa415165c26f74cfa17462132996186b/flask_limiter-3.12-py3-none-any.whl", hash = "sha256:b94c9e9584df98209542686947cf647f1ede35ed7e4ab564934a2bb9ed46b143", size = 28490, upload-time = "2025-03-15T02:23:08.919Z" }, +] + +[package.optional-dependencies] +redis = [ + { name = "limits", extra = ["redis"] }, +] + +[[package]] +name = "flask-marshmallow" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "marshmallow" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/71/fa8f87753c0c1a42c45a32f7048dff79993cf45cf62d89ef53f87412f3c8/flask-marshmallow-0.14.0.tar.gz", hash = "sha256:bd01a6372cbe50e36f205cfff0fc5dab0b7b662c4c8b2c4fc06a3151b2950950", size = 36001, upload-time = "2020-09-28T03:38:47.622Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/a1/eb163776684d5f03ca34a582fe4859ad9acfbb9835aed2e5c87162931a22/flask_marshmallow-0.14.0-py2.py3-none-any.whl", hash = "sha256:2adcd782b5a4a6c5ae3c96701f320d8ca6997995a52b2661093c56cc3ed24754", size = 10039, upload-time = "2020-09-28T03:38:46.517Z" }, +] + +[[package]] +name = "flask-restx" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aniso8601" }, + { name = "flask" }, + { name = "importlib-resources" }, + { name = "jsonschema" }, + { name = "pytz" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/4c/2e7d84e2b406b47cf3bf730f521efe474977b404ee170d8ea68dc37e6733/flask-restx-1.3.0.tar.gz", hash = "sha256:4f3d3fa7b6191fcc715b18c201a12cd875176f92ba4acc61626ccfd571ee1728", size = 2814072, upload-time = "2023-12-10T14:48:55.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/bf/1907369f2a7ee614dde5152ff8f811159d357e77962aa3f8c2e937f63731/flask_restx-1.3.0-py2.py3-none-any.whl", hash = "sha256:636c56c3fb3f2c1df979e748019f084a938c4da2035a3e535a4673e4fc177691", size = 2798683, upload-time = "2023-12-10T14:48:53.293Z" }, +] + +[[package]] +name = "flask-sqlalchemy" +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/f0/39dd2d8e7e5223f78a5206d7020dc0e16718a964acfb3564d89e9798ab9b/Flask-SQLAlchemy-2.5.1.tar.gz", hash = "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912", size = 132750, upload-time = "2021-03-18T19:03:02.733Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/2c/9088b6bd95bca539230bbe9ad446737ed391aab9a83aff403e18dded3e75/Flask_SQLAlchemy-2.5.1-py2.py3-none-any.whl", hash = "sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390", size = 17716, upload-time = "2021-03-18T19:03:00.702Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" }, + { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" }, + { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" }, + { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" }, + { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" }, + { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" }, + { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" }, + { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" }, + { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" }, + { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/f7/27f15d41f0ed38e8fcc488584b57e902b331da7f7c6dcda53721b15838fc/fsspec-2025.5.1.tar.gz", hash = "sha256:2e55e47a540b91843b755e83ded97c6e897fa0942b11490113f09e9c443c2475", size = 303033, upload-time = "2025-05-24T12:03:23.792Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/61/78c7b3851add1481b048b5fdc29067397a1784e2910592bc81bb3f608635/fsspec-2025.5.1-py3-none-any.whl", hash = "sha256:24d3a2e663d5fc735ab256263c4075f374a174c3410c0b25e5bd1970bceaa462", size = 199052, upload-time = "2025-05-24T12:03:21.66Z" }, +] + +[[package]] +name = "furl" +version = "2.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "orderedmultidict" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/e4/203a76fa2ef46cdb0a618295cc115220cbb874229d4d8721068335eb87f0/furl-2.1.4.tar.gz", hash = "sha256:877657501266c929269739fb5f5980534a41abd6bbabcb367c136d1d3b2a6015", size = 57526, upload-time = "2025-03-09T05:36:21.175Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/8c/dce3b1b7593858eba995b2dfdb833f872c7f863e3da92aab7128a6b11af4/furl-2.1.4-py2.py3-none-any.whl", hash = "sha256:da34d0b34e53ffe2d2e6851a7085a05d96922b5b578620a37377ff1dbeeb11c8", size = 27550, upload-time = "2025-03-09T05:36:19.928Z" }, +] + +[[package]] +name = "gcsfs" +version = "2025.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "decorator" }, + { name = "fsspec" }, + { name = "google-auth" }, + { name = "google-auth-oauthlib" }, + { name = "google-cloud-storage" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/4a/47ad326cc74ccfd97e125c0087a36d516ed74c61f53e458067737378d0f2/gcsfs-2025.5.1.tar.gz", hash = "sha256:ba945530cf4857cd9d599ccb3ae729c65c39088880b11c4df1fecac30df5f3e3", size = 82173, upload-time = "2025-05-24T12:12:58.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/eb/9182e875592c48d282c5eab602000f0618817b9011b2b2925165e4b4b7f3/gcsfs-2025.5.1-py2.py3-none-any.whl", hash = "sha256:48712471ff71ac83d3e2152ba4dc232874698466e344d5e700feba06b0a0de7b", size = 36581, upload-time = "2025-05-24T12:12:57.011Z" }, +] + +[[package]] +name = "geoalchemy2" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/d8/99ad10735441b069b840efc2ae52645722e6f008bd568da39a718c31e584/GeoAlchemy2-0.11.1.tar.gz", hash = "sha256:f92a0faddb5b74384dbbf3c7000433358ce8e07a180fe1d6c2843eaa0437ff08", size = 147467, upload-time = "2022-03-05T20:27:06.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/64/450282774874e3b53b4bcbc736cf6ed8dd1f1fa8e6ebc38c364cfccd5df4/GeoAlchemy2-0.11.1-py2.py3-none-any.whl", hash = "sha256:43ce38a5387c9b380b4c9d20d720c3d8cf1752ca4f5f18aa88041be11f6948a3", size = 45653, upload-time = "2022-03-05T20:27:04.676Z" }, +] + +[[package]] +name = "gevent" +version = "25.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'CPython' and sys_platform == 'win32'" }, + { name = "greenlet", marker = "platform_python_implementation == 'CPython'" }, + { name = "zope-event" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/58/267e8160aea00ab00acd2de97197eecfe307064a376fb5c892870a8a6159/gevent-25.5.1.tar.gz", hash = "sha256:582c948fa9a23188b890d0bc130734a506d039a2e5ad87dae276a456cc683e61", size = 6388207, upload-time = "2025-05-12T12:57:59.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/c5/cf71423666a0b83db3d7e3f85788bc47d573fca5fe62b798fe2c4273de7c/gevent-25.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d87c0a1bd809d8f70f96b9b229779ec6647339830b8888a192beed33ac8d129f", size = 2909333, upload-time = "2025-05-12T11:11:34.883Z" }, + { url = "https://files.pythonhosted.org/packages/26/7e/d2f174ee8bec6eb85d961ca203bc599d059c857b8412e367b8fa206603a5/gevent-25.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b87a4b66edb3808d4d07bbdb0deed5a710cf3d3c531e082759afd283758bb649", size = 1788420, upload-time = "2025-05-12T11:52:30.306Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f3/3aba8c147b9108e62ba348c726fe38ae69735a233db425565227336e8ce6/gevent-25.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f076779050029a82feb0cb1462021d3404d22f80fa76a181b1a7889cd4d6b519", size = 1868854, upload-time = "2025-05-12T11:54:21.564Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b1/11a5453f8fcebe90a456471fad48bd154c6a62fcb96e3475a5e408d05fc8/gevent-25.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb673eb291c19370f69295f7a881a536451408481e2e3deec3f41dedb7c281ec", size = 1833946, upload-time = "2025-05-12T12:00:05.514Z" }, + { url = "https://files.pythonhosted.org/packages/70/1c/37d4a62303f86e6af67660a8df38c1171b7290df61b358e618c6fea79567/gevent-25.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1325ed44225c8309c0dd188bdbbbee79e1df8c11ceccac226b861c7d52e4837", size = 2070583, upload-time = "2025-05-12T11:33:02.803Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8f/3b14929ff28263aba1d268ea97bcf104be1a86ba6f6bb4633838e7a1905e/gevent-25.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fcd5bcad3102bde686d0adcc341fade6245186050ce14386d547ccab4bd54310", size = 1808341, upload-time = "2025-05-12T11:59:59.154Z" }, + { url = "https://files.pythonhosted.org/packages/2f/fc/674ec819fb8a96e482e4d21f8baa43d34602dba09dfce7bbdc8700899d1b/gevent-25.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a93062609e8fa67ec97cd5fb9206886774b2a09b24887f40148c9c37e6fb71c", size = 2137974, upload-time = "2025-05-12T11:40:54.78Z" }, + { url = "https://files.pythonhosted.org/packages/05/9a/048b7f5e28c54e4595ad4a8ad3c338fa89560e558db2bbe8273f44f030de/gevent-25.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:2534c23dc32bed62b659ed4fd9e198906179e68b26c9276a897e04163bdde806", size = 1638344, upload-time = "2025-05-12T12:08:31.776Z" }, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "google-api-core" +version = "2.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/21/e9d043e88222317afdbdb567165fdbc3b0aad90064c7e0c9eb0ad9955ad8/google_api_core-2.25.1.tar.gz", hash = "sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8", size = 165443, upload-time = "2025-06-12T20:52:20.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/4b/ead00905132820b623732b175d66354e9d3e69fcf2a5dcdab780664e7896/google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7", size = 160807, upload-time = "2025-06-12T20:52:19.334Z" }, +] + +[[package]] +name = "google-apitools" +version = "0.5.32" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fasteners" }, + { name = "httplib2" }, + { name = "oauth2client" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/eb/c26c36463a769a3a9f08847b9bf218cb629ca91877a911bbd6dcf37d9e62/google-apitools-0.5.32.tar.gz", hash = "sha256:c3763e52289f61e21c41d5531e20fbda9cc8484a088b8686fd460770db8bad13", size = 172810, upload-time = "2021-05-05T22:13:00.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/cb/cb0311f2ec371c83d6510847476c665edc9cc97564a51923557bc8f0b680/google_apitools-0.5.32-py3-none-any.whl", hash = "sha256:b78f74116558e0476e19501b5b4b2ac7c93261a69c5449c861ea95cbc853c688", size = 135651, upload-time = "2021-05-05T22:12:58.355Z" }, +] + +[[package]] +name = "google-auth" +version = "2.40.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, +] + +[[package]] +name = "google-auth-oauthlib" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "requests-oauthlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/87/e10bf24f7bcffc1421b84d6f9c3377c30ec305d082cd737ddaa6d8f77f7c/google_auth_oauthlib-1.2.2.tar.gz", hash = "sha256:11046fb8d3348b296302dd939ace8af0a724042e8029c1b872d87fabc9f41684", size = 20955, upload-time = "2025-04-22T16:40:29.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/84/40ee070be95771acd2f4418981edb834979424565c3eec3cd88b6aa09d24/google_auth_oauthlib-1.2.2-py3-none-any.whl", hash = "sha256:fd619506f4b3908b5df17b65f39ca8d66ea56986e5472eb5978fd8f3786f00a2", size = 19072, upload-time = "2025-04-22T16:40:28.174Z" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861, upload-time = "2025-03-10T21:05:38.948Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348, upload-time = "2025-03-10T21:05:37.785Z" }, +] + +[[package]] +name = "google-cloud-storage" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-crc32c" }, + { name = "google-resumable-media" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/84/6afc2ffdf31f6247a6bab6ba070e073fb05e0fda56adf59ce52ac591a033/google_cloud_storage-3.1.1.tar.gz", hash = "sha256:f9c8f965cafd1d38509f8e2b070339e0e9e5bf050774653bf36213d4ea6104c0", size = 7668109, upload-time = "2025-06-18T11:06:52.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/4f/b922e919f6e1ea5905f1427fadf1a3f56a85e79e2b0037fec182f6b437dd/google_cloud_storage-3.1.1-py3-none-any.whl", hash = "sha256:ba7e6ae2be5a7a08742f001e23ec6a0c17d78c620f63bf8e0e7c2cbdddb407de", size = 175464, upload-time = "2025-06-18T11:06:51.043Z" }, +] + +[[package]] +name = "google-crc32c" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495, upload-time = "2025-03-26T14:29:13.32Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/b7/787e2453cf8639c94b3d06c9d61f512234a82e1d12d13d18584bd3049904/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194", size = 30470, upload-time = "2025-03-26T14:34:31.655Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b4/6042c2b0cbac3ec3a69bb4c49b28d2f517b7a0f4a0232603c42c58e22b44/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e", size = 30315, upload-time = "2025-03-26T15:01:54.634Z" }, + { url = "https://files.pythonhosted.org/packages/29/ad/01e7a61a5d059bc57b702d9ff6a18b2585ad97f720bd0a0dbe215df1ab0e/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337", size = 33180, upload-time = "2025-03-26T14:41:32.168Z" }, + { url = "https://files.pythonhosted.org/packages/3b/a5/7279055cf004561894ed3a7bfdf5bf90a53f28fadd01af7cd166e88ddf16/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65", size = 32794, upload-time = "2025-03-26T14:41:33.264Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d6/77060dbd140c624e42ae3ece3df53b9d811000729a5c821b9fd671ceaac6/google_crc32c-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6", size = 33477, upload-time = "2025-03-26T14:29:10.94Z" }, +] + +[[package]] +name = "google-resumable-media" +version = "2.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-crc32c" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/5a/0efdc02665dca14e0837b62c8a1a93132c264bd02054a15abb2218afe0ae/google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0", size = 2163099, upload-time = "2024-08-07T22:20:38.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/35/b8d3baf8c46695858cb9d8835a53baa1eeb9906ddaf2f728a5f5b640fd1e/google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", size = 81251, upload-time = "2024-08-07T22:20:36.409Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.70.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903, upload-time = "2025-04-14T10:17:02.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", size = 185752, upload-time = "2025-06-05T16:16:09.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/94/ad0d435f7c48debe960c53b8f60fb41c2026b1d0fa4a99a1cb17c3461e09/greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d", size = 271992, upload-time = "2025-06-05T16:11:23.467Z" }, + { url = "https://files.pythonhosted.org/packages/93/5d/7c27cf4d003d6e77749d299c7c8f5fd50b4f251647b5c2e97e1f20da0ab5/greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b", size = 638820, upload-time = "2025-06-05T16:38:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7e/807e1e9be07a125bb4c169144937910bf59b9d2f6d931578e57f0bce0ae2/greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d", size = 653046, upload-time = "2025-06-05T16:41:36.343Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ab/158c1a4ea1068bdbc78dba5a3de57e4c7aeb4e7fa034320ea94c688bfb61/greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264", size = 647701, upload-time = "2025-06-05T16:48:19.604Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0d/93729068259b550d6a0288da4ff72b86ed05626eaf1eb7c0d3466a2571de/greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688", size = 649747, upload-time = "2025-06-05T16:13:04.628Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f6/c82ac1851c60851302d8581680573245c8fc300253fc1ff741ae74a6c24d/greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb", size = 605461, upload-time = "2025-06-05T16:12:50.792Z" }, + { url = "https://files.pythonhosted.org/packages/98/82/d022cf25ca39cf1200650fc58c52af32c90f80479c25d1cbf57980ec3065/greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c", size = 1121190, upload-time = "2025-06-05T16:36:48.59Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e1/25297f70717abe8104c20ecf7af0a5b82d2f5a980eb1ac79f65654799f9f/greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163", size = 1149055, upload-time = "2025-06-05T16:12:40.457Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8f/8f9e56c5e82eb2c26e8cde787962e66494312dc8cb261c460e1f3a9c88bc/greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849", size = 297817, upload-time = "2025-06-05T16:29:49.244Z" }, +] + +[[package]] +name = "griffe" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/3e/5aa9a61f7c3c47b0b52a1d930302992229d191bf4bc76447b324b731510a/griffe-1.7.3.tar.gz", hash = "sha256:52ee893c6a3a968b639ace8015bec9d36594961e156e23315c8e8e51401fa50b", size = 395137, upload-time = "2025-04-23T11:29:09.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/c6/5c20af38c2a57c15d87f7f38bee77d63c1d2a3689f74fefaf35915dd12b2/griffe-1.7.3-py3-none-any.whl", hash = "sha256:c6b3ee30c2f0f17f30bcdef5068d6ab7a2a4f1b8bf1a3e74b56fffd21e1c5f75", size = 129303, upload-time = "2025-04-23T11:29:07.145Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httplib2" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/ad/2371116b22d616c194aa25ec410c9c6c37f23599dcd590502b74db197584/httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81", size = 351116, upload-time = "2023-03-21T22:29:37.214Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/6c/d2fbdaaa5959339d53ba38e94c123e4e84b8fbc4b84beb0e70d7c1608486/httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", size = 96854, upload-time = "2023-03-21T22:29:35.683Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, +] + +[[package]] +name = "inflection" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/7e/691d061b7329bc8d54edbf0ec22fbfb2afe61facb681f9aaa9bff7a27d04/inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", size = 15091, upload-time = "2020-08-22T08:16:29.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454, upload-time = "2020-08-22T08:16:27.816Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "ipykernel" +version = "6.29.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367, upload-time = "2024-07-01T14:07:22.543Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173, upload-time = "2024-07-01T14:07:19.603Z" }, +] + +[[package]] +name = "ipython" +version = "9.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/80/406f9e3bde1c1fd9bf5a0be9d090f8ae623e401b7670d8f6fdf2ab679891/ipython-9.4.0.tar.gz", hash = "sha256:c033c6d4e7914c3d9768aabe76bbe87ba1dc66a92a05db6bfa1125d81f2ee270", size = 4385338, upload-time = "2025-07-01T11:11:30.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/f8/0031ee2b906a15a33d6bfc12dd09c3dfa966b3cb5b284ecfb7549e6ac3c4/ipython-9.4.0-py3-none-any.whl", hash = "sha256:25850f025a446d9b359e8d296ba175a36aedd32e83ca9b5060430fe16801f066", size = 611021, upload-time = "2025-07-01T11:11:27.85Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, +] + +[[package]] +name = "json5" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/be/c6c745ec4c4539b25a278b70e29793f10382947df0d9efba2fa09120895d/json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a", size = 51907, upload-time = "2025-04-03T16:33:13.201Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/9f/3500910d5a98549e3098807493851eeef2b89cdd3032227558a104dfe926/json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db", size = 36079, upload-time = "2025-04-03T16:33:11.927Z" }, +] + +[[package]] +name = "jsonschema" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "pyrsistent" }, + { name = "setuptools" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/11/a69e2a3c01b324a77d3a7c0570faa372e8448b666300c4117a516f8b1212/jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a", size = 167226, upload-time = "2019-11-18T12:57:10.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/8f/51e89ce52a085483359217bc72cdbf6e75ee595d5b1d4b5ade40c7e018b8/jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", size = 56305, upload-time = "2019-11-18T12:57:08.454Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/1b/72906d554acfeb588332eaaa6f61577705e9ec752ddb486f302dafa292d9/jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941", size = 88923, upload-time = "2025-05-27T07:38:16.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880, upload-time = "2025-05-27T07:38:15.137Z" }, +] + +[[package]] +name = "kombu" +version = "5.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "amqp" }, + { name = "packaging" }, + { name = "tzdata" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/d3/5ff936d8319ac86b9c409f1501b07c426e6ad41966fedace9ef1b966e23f/kombu-5.5.4.tar.gz", hash = "sha256:886600168275ebeada93b888e831352fe578168342f0d1d5833d88ba0d847363", size = 461992, upload-time = "2025-06-01T10:19:22.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", hash = "sha256:a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8", size = 210034, upload-time = "2025-06-01T10:19:20.436Z" }, +] + +[[package]] +name = "limits" +version = "5.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecated" }, + { name = "packaging" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/32/95d4908a730213a5db40462b0e20c1b93a688b33eade8c4981bbf0ca08de/limits-5.4.0.tar.gz", hash = "sha256:27ebf55118e3c9045f0dbc476f4559b26d42f4b043db670afb8963f36cf07fd9", size = 95423, upload-time = "2025-06-16T16:18:53.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/aa/b84c06700735332017bc095182756ee9fb71db650d89b50b6d63549c6fcd/limits-5.4.0-py3-none-any.whl", hash = "sha256:1afb03c0624cf004085532aa9524953f2565cf8b0a914e48dda89d172c13ceb7", size = 60950, upload-time = "2025-06-16T16:18:51.593Z" }, +] + +[package.optional-dependencies] +redis = [ + { name = "redis" }, +] + +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + +[[package]] +name = "markdown" +version = "3.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, +] + +[[package]] +name = "marshmallow" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/74/5aa84008ddc6e8fee93d961a9f04a745a349ad197d95ab89723c097b330d/marshmallow-3.5.1.tar.gz", hash = "sha256:90854221bbb1498d003a0c3cc9d8390259137551917961c8b5258c64026b2f85", size = 168839, upload-time = "2020-03-05T13:05:22.691Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/0b/bb43d7610e71d87e8537025841c248471dbf938c676d32b8f94c82148c04/marshmallow-3.5.1-py2.py3-none-any.whl", hash = "sha256:ac2e13b30165501b7d41fc0371b8df35944f5849769d136f20e2c5f6cdc6e665", size = 45466, upload-time = "2020-03-05T13:05:20.586Z" }, +] + +[[package]] +name = "marshmallow-jsonschema" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/ac/6f566459ab5e2e574c5670f4ab1d0e4aea265cf11f9539369dd0bb7063ef/marshmallow-jsonschema-0.10.0.tar.gz", hash = "sha256:97b4720b719195007bb73550f4a68a6a4fa30ff160057b33997b8c76996259f0", size = 13679, upload-time = "2020-03-03T18:50:26.286Z" } + +[[package]] +name = "marshmallow-sqlalchemy" +version = "0.28.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "packaging" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/84/9cced63c2e1bbd4f243f5aed0a4eaf018ef97475e4eecf388bed4d5033b8/marshmallow-sqlalchemy-0.28.2.tar.gz", hash = "sha256:2ab0f1280c793e5aec81deab3e63ec23688ddfe05e5f38ac960368a1079520a1", size = 52156, upload-time = "2023-02-23T22:39:08.931Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/15/0c63bbbd7c21e44065ce7e198c0e515a98d2e37e5f5694d69595285dd67f/marshmallow_sqlalchemy-0.28.2-py2.py3-none-any.whl", hash = "sha256:c31b3bdf794de1d78c53e1c495502cbb3eeb06ed216869980c71d6159e7e9e66", size = 16095, upload-time = "2023-02-23T22:39:06.198Z" }, +] + +[[package]] +name = "materializationengine" +version = "5.10.0" +source = { editable = "." } +dependencies = [ + { name = "alembic" }, + { name = "cachetools" }, + { name = "caveclient" }, + { name = "celery" }, + { name = "cloud-files" }, + { name = "cloud-volume" }, + { name = "cryptography" }, + { name = "dynamicannotationdb" }, + { name = "emannotationschemas" }, + { name = "flask" }, + { name = "flask-accepts" }, + { name = "flask-admin" }, + { name = "flask-cors" }, + { name = "flask-limiter", extra = ["redis"] }, + { name = "flask-marshmallow" }, + { name = "flask-restx" }, + { name = "flask-sqlalchemy" }, + { name = "gcsfs" }, + { name = "geoalchemy2" }, + { name = "gevent" }, + { name = "jsonschema" }, + { name = "marshmallow-sqlalchemy" }, + { name = "middle-auth-client" }, + { name = "multiwrapper" }, + { name = "nglui" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "psutil" }, + { name = "pyarrow" }, + { name = "requests" }, + { name = "sqlalchemy" }, + { name = "uwsgi" }, +] + +[package.dev-dependencies] +dev = [ + { name = "bump-my-version" }, + { name = "docker" }, + { name = "ipykernel" }, + { name = "pyopenssl" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-env" }, +] +docs = [ + { name = "mkdocs" }, + { name = "mkdocs-material" }, + { name = "mkdocstrings", extra = ["python"] }, +] +lint = [ + { name = "ruff" }, +] +profile = [ + { name = "pyinstrument" }, + { name = "scalene" }, +] + +[package.metadata] +requires-dist = [ + { name = "alembic" }, + { name = "cachetools" }, + { name = "caveclient", specifier = ">=7.7.0" }, + { name = "celery", specifier = ">=5.2.3" }, + { name = "cloud-files", specifier = ">=4.6.1" }, + { name = "cloud-volume", specifier = ">=8.5.4" }, + { name = "cryptography", specifier = ">=44.0.2" }, + { name = "dynamicannotationdb", specifier = ">=5.13.1" }, + { name = "emannotationschemas", specifier = ">=5.24.12" }, + { name = "flask", specifier = ">=2.0.2,<3.0" }, + { name = "flask-accepts" }, + { name = "flask-admin" }, + { name = "flask-cors" }, + { name = "flask-limiter", extras = ["redis"] }, + { name = "flask-marshmallow", specifier = "==0.14.0" }, + { name = "flask-restx" }, + { name = "flask-sqlalchemy" }, + { name = "gcsfs", specifier = ">=0.8.0" }, + { name = "geoalchemy2", specifier = ">=0.9.2" }, + { name = "gevent" }, + { name = "jsonschema" }, + { name = "marshmallow-sqlalchemy" }, + { name = "middle-auth-client", specifier = ">=3.19.0" }, + { name = "multiwrapper" }, + { name = "nglui", specifier = ">=3.2.1,<4" }, + { name = "numpy", specifier = ">=1.20" }, + { name = "pandas" }, + { name = "pillow", specifier = ">=8.3.2" }, + { name = "psutil", specifier = ">=5.6.6" }, + { name = "pyarrow" }, + { name = "requests", specifier = ">=2.26.0" }, + { name = "sqlalchemy", specifier = "<1.4" }, + { name = "uwsgi", specifier = ">=2.0.30" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "bump-my-version" }, + { name = "docker" }, + { name = "ipykernel" }, + { name = "pyopenssl" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-env" }, +] +docs = [ + { name = "mkdocs" }, + { name = "mkdocs-material" }, + { name = "mkdocstrings", extras = ["python"] }, +] +lint = [{ name = "ruff" }] +profile = [ + { name = "pyinstrument" }, + { name = "scalene" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159, upload-time = "2024-04-15T13:44:44.803Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "microviewer" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/2f/530fdc55669d32a67d3f21efe0f622d3639378be6370293b828a4ff923f6/microviewer-1.16.0.tar.gz", hash = "sha256:f74992bb09036004e7dc176705fe7cf404e48fd26360dbe7131bc9ea063466f8", size = 1483650, upload-time = "2025-06-17T04:03:23.82Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/2a/91a7117adf6c0a7310420140390c980bee1c59b016f3b6966ef87f12988d/microviewer-1.16.0-py3-none-any.whl", hash = "sha256:2a66e9dec8e1d0c5e28fd34a8ac58d406ff8b3ec059be7938da0ee3232bcefc7", size = 159934, upload-time = "2025-06-17T04:03:22.318Z" }, +] + +[[package]] +name = "middle-auth-client" +version = "3.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "flask" }, + { name = "furl" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/86/ea8d315361daef80e0c6700ff3b6c58aaba987282a640402e19c6442207a/middle_auth_client-3.19.0.tar.gz", hash = "sha256:f43d51e4a680ccd9542f305e042c3992d35420cf45333b5987455c36831fa839", size = 8039, upload-time = "2025-07-09T16:52:30.096Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/40/4c3570ad1811cb8c8c3d5de9a022904add531f5010a948af08b00a11fa11/middle_auth_client-3.19.0-py3-none-any.whl", hash = "sha256:f16f17df5789e3a329a3ee6cc8614882564aff046b542b2963648b100febde65", size = 8162, upload-time = "2025-07-09T16:52:29.332Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/0c/c9826f35b99c67fa3a7cddfa094c1a6c43fafde558c309c6e4403e5b37dc/mkdocs_autorefs-1.4.2.tar.gz", hash = "sha256:e2ebe1abd2b67d597ed19378c0fff84d73d1dbce411fce7a7cc6f161888b6749", size = 54961, upload-time = "2025-05-20T13:09:09.886Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/dc/fc063b78f4b769d1956319351704e23ebeba1e9e1d6a41b4b602325fd7e4/mkdocs_autorefs-1.4.2-py3-none-any.whl", hash = "sha256:83d6d777b66ec3c372a1aad4ae0cf77c243ba5bcda5bf0c6b8a2c5e7a3d89f13", size = 24969, upload-time = "2025-05-20T13:09:08.237Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.6.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/c1/f804ba2db2ddc2183e900befe7dad64339a34fa935034e1ab405289d0a97/mkdocs_material-9.6.15.tar.gz", hash = "sha256:64adf8fa8dba1a17905b6aee1894a5aafd966d4aeb44a11088519b0f5ca4f1b5", size = 3951836, upload-time = "2025-07-01T10:14:15.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/30/dda19f0495a9096b64b6b3c07c4bfcff1c76ee0fc521086d53593f18b4c0/mkdocs_material-9.6.15-py3-none-any.whl", hash = "sha256:ac969c94d4fe5eb7c924b6d2f43d7db41159ea91553d18a9afc4780c34f2717a", size = 8716840, upload-time = "2025-07-01T10:14:13.18Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "mkdocstrings" +version = "0.29.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/e8/d22922664a627a0d3d7ff4a6ca95800f5dde54f411982591b4621a76225d/mkdocstrings-0.29.1.tar.gz", hash = "sha256:8722f8f8c5cd75da56671e0a0c1bbed1df9946c0cef74794d6141b34011abd42", size = 1212686, upload-time = "2025-03-31T08:33:11.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/14/22533a578bf8b187e05d67e2c1721ce10e3f526610eebaf7a149d557ea7a/mkdocstrings-0.29.1-py3-none-any.whl", hash = "sha256:37a9736134934eea89cbd055a513d40a020d87dfcae9e3052c2a6b8cd4af09b6", size = 1631075, upload-time = "2025-03-31T08:33:09.661Z" }, +] + +[package.optional-dependencies] +python = [ + { name = "mkdocstrings-python" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "1.16.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffe" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/ed/b886f8c714fd7cccc39b79646b627dbea84cd95c46be43459ef46852caf0/mkdocstrings_python-1.16.12.tar.gz", hash = "sha256:9b9eaa066e0024342d433e332a41095c4e429937024945fea511afe58f63175d", size = 206065, upload-time = "2025-06-03T12:52:49.276Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/dd/a24ee3de56954bfafb6ede7cd63c2413bb842cc48eb45e41c43a05a33074/mkdocstrings_python-1.16.12-py3-none-any.whl", hash = "sha256:22ded3a63b3d823d57457a70ff9860d5a4de9e8b1e482876fc9baabaf6f5f374", size = 124287, upload-time = "2025-06-03T12:52:47.819Z" }, +] + +[[package]] +name = "multidict" +version = "6.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006, upload-time = "2025-06-30T15:53:46.929Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/a0/6b57988ea102da0623ea814160ed78d45a2645e4bbb499c2896d12833a70/multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6", size = 76514, upload-time = "2025-06-30T15:51:48.728Z" }, + { url = "https://files.pythonhosted.org/packages/07/7a/d1e92665b0850c6c0508f101f9cf0410c1afa24973e1115fe9c6a185ebf7/multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f", size = 45394, upload-time = "2025-06-30T15:51:49.986Z" }, + { url = "https://files.pythonhosted.org/packages/52/6f/dd104490e01be6ef8bf9573705d8572f8c2d2c561f06e3826b081d9e6591/multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55", size = 43590, upload-time = "2025-06-30T15:51:51.331Z" }, + { url = "https://files.pythonhosted.org/packages/44/fe/06e0e01b1b0611e6581b7fd5a85b43dacc08b6cea3034f902f383b0873e5/multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b", size = 237292, upload-time = "2025-06-30T15:51:52.584Z" }, + { url = "https://files.pythonhosted.org/packages/ce/71/4f0e558fb77696b89c233c1ee2d92f3e1d5459070a0e89153c9e9e804186/multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888", size = 258385, upload-time = "2025-06-30T15:51:53.913Z" }, + { url = "https://files.pythonhosted.org/packages/e3/25/cca0e68228addad24903801ed1ab42e21307a1b4b6dd2cf63da5d3ae082a/multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d", size = 242328, upload-time = "2025-06-30T15:51:55.672Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a3/46f2d420d86bbcb8fe660b26a10a219871a0fbf4d43cb846a4031533f3e0/multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680", size = 268057, upload-time = "2025-06-30T15:51:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/9e/73/1c743542fe00794a2ec7466abd3f312ccb8fad8dff9f36d42e18fb1ec33e/multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a", size = 269341, upload-time = "2025-06-30T15:51:59.111Z" }, + { url = "https://files.pythonhosted.org/packages/a4/11/6ec9dcbe2264b92778eeb85407d1df18812248bf3506a5a1754bc035db0c/multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961", size = 256081, upload-time = "2025-06-30T15:52:00.533Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2b/631b1e2afeb5f1696846d747d36cda075bfdc0bc7245d6ba5c319278d6c4/multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65", size = 253581, upload-time = "2025-06-30T15:52:02.43Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0e/7e3b93f79efeb6111d3bf9a1a69e555ba1d07ad1c11bceb56b7310d0d7ee/multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643", size = 250750, upload-time = "2025-06-30T15:52:04.26Z" }, + { url = "https://files.pythonhosted.org/packages/ad/9e/086846c1d6601948e7de556ee464a2d4c85e33883e749f46b9547d7b0704/multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063", size = 251548, upload-time = "2025-06-30T15:52:06.002Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7b/86ec260118e522f1a31550e87b23542294880c97cfbf6fb18cc67b044c66/multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3", size = 262718, upload-time = "2025-06-30T15:52:07.707Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bd/22ce8f47abb0be04692c9fc4638508b8340987b18691aa7775d927b73f72/multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75", size = 259603, upload-time = "2025-06-30T15:52:09.58Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/91b7ac1691be95cd1f4a26e36a74b97cda6aa9820632d31aab4410f46ebd/multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10", size = 251351, upload-time = "2025-06-30T15:52:10.947Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5c/4d7adc739884f7a9fbe00d1eac8c034023ef8bad71f2ebe12823ca2e3649/multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5", size = 41860, upload-time = "2025-06-30T15:52:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a3/0fbc7afdf7cb1aa12a086b02959307848eb6bcc8f66fcb66c0cb57e2a2c1/multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17", size = 45982, upload-time = "2025-06-30T15:52:13.6Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/8c825bd70ff9b02462dc18d1295dd08d3e9e4eb66856d292ffa62cfe1920/multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b", size = 43210, upload-time = "2025-06-30T15:52:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313, upload-time = "2025-06-30T15:53:45.437Z" }, +] + +[[package]] +name = "multiprocess" +version = "0.70.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/fd/2ae3826f5be24c6ed87266bc4e59c46ea5b059a103f3d7e7eb76a52aeecb/multiprocess-0.70.18.tar.gz", hash = "sha256:f9597128e6b3e67b23956da07cf3d2e5cba79e2f4e0fba8d7903636663ec6d0d", size = 1798503, upload-time = "2025-04-17T03:11:27.742Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/d8/0cba6cf51a1a31f20471fbc823a716170c73012ddc4fb85d706630ed6e8f/multiprocess-0.70.18-py310-none-any.whl", hash = "sha256:60c194974c31784019c1f459d984e8f33ee48f10fcf42c309ba97b30d9bd53ea", size = 134948, upload-time = "2025-04-17T03:11:20.223Z" }, + { url = "https://files.pythonhosted.org/packages/4b/88/9039f2fed1012ef584751d4ceff9ab4a51e5ae264898f0b7cbf44340a859/multiprocess-0.70.18-py311-none-any.whl", hash = "sha256:5aa6eef98e691281b3ad923be2832bf1c55dd2c859acd73e5ec53a66aae06a1d", size = 144462, upload-time = "2025-04-17T03:11:21.657Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b6/5f922792be93b82ec6b5f270bbb1ef031fd0622847070bbcf9da816502cc/multiprocess-0.70.18-py312-none-any.whl", hash = "sha256:9b78f8e5024b573730bfb654783a13800c2c0f2dfc0c25e70b40d184d64adaa2", size = 150287, upload-time = "2025-04-17T03:11:22.69Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c3/ca84c19bd14cdfc21c388fdcebf08b86a7a470ebc9f5c3c084fc2dbc50f7/multiprocess-0.70.18-py38-none-any.whl", hash = "sha256:dbf705e52a154fe5e90fb17b38f02556169557c2dd8bb084f2e06c2784d8279b", size = 132636, upload-time = "2025-04-17T03:11:24.936Z" }, + { url = "https://files.pythonhosted.org/packages/6c/28/dd72947e59a6a8c856448a5e74da6201cb5502ddff644fbc790e4bd40b9a/multiprocess-0.70.18-py39-none-any.whl", hash = "sha256:e78ca805a72b1b810c690b6b4cc32579eba34f403094bbbae962b7b5bf9dfcb8", size = 133478, upload-time = "2025-04-17T03:11:26.253Z" }, +] + +[[package]] +name = "multiwrapper" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/b2/4664f574f330d5e336b1d2adead3db306a90b54c73ad99e7803bffe9e61b/multiwrapper-0.1.1.tar.gz", hash = "sha256:36e12cdef1c0b5fc84cdc0556c6fa518d9e1ff26ba22ba771b8eb06acb2d2985", size = 4904, upload-time = "2018-09-20T20:53:09.603Z" } + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "networkx" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, +] + +[[package]] +name = "neuroglancer" +version = "2.40.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "atomicwrites" }, + { name = "google-apitools" }, + { name = "google-auth" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "requests" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/68/600011755c7873d69431c2662bf631414e417d283b8cc7976dfa66178800/neuroglancer-2.40.1.tar.gz", hash = "sha256:ba21309827810d669a2f76e7b814a123aafeb61b7cb07b2de11a9663ad902fdc", size = 3640302, upload-time = "2024-07-23T20:24:34.827Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/52/3890bdc3345a1885a6eac6d4b982124d4820c11b706ce179b76c26b3eaea/neuroglancer-2.40.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:73b214772263005e2531dab6051ed1f4d5cd4c4f1c6eb181b9baf704ecb6f3f3", size = 3649794, upload-time = "2024-07-23T20:24:25.667Z" }, + { url = "https://files.pythonhosted.org/packages/a7/37/214037aec09f43e1854173fc338eb5f20f2b2f6d57ccf9dfc9f4032b1567/neuroglancer-2.40.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7466279b2cad28465edc727b994949599d2f0ece9ca22cee05bfa30c1d63a1ac", size = 3644923, upload-time = "2024-07-23T20:24:28.251Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a2/e0e01c70d7a6804eb00faeb901af341ca5518b9d58c444c133d300c48b83/neuroglancer-2.40.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5cb9583b1668aad49c092bb047d6e74959bb3b5b343d5a624f084d82f3ede60", size = 5138950, upload-time = "2024-07-23T20:24:29.989Z" }, + { url = "https://files.pythonhosted.org/packages/23/d9/29ae513f60f0d972789453f14b22753d9becc303f0ad7e797cf6b4878580/neuroglancer-2.40.1-cp39-abi3-win_amd64.whl", hash = "sha256:dc70a9dcd438c763515a2ca36135d10a49bd3261ac3fcf0b9183c03411554295", size = 3622988, upload-time = "2024-07-23T20:24:31.834Z" }, +] + +[[package]] +name = "nglui" +version = "3.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "caveclient" }, + { name = "ipython" }, + { name = "neuroglancer" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "python-box" }, + { name = "requests" }, + { name = "six" }, + { name = "webcolors" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/6b/cf803db85f0efa0ee93cf65f21108ed063781f0c834d9360dbf7b3868538/nglui-3.8.2.tar.gz", hash = "sha256:b1cb3d82e6b1749504ef9aa943c34d5d934814f8992cea203d4611ee8935e9e8", size = 983496, upload-time = "2025-03-27T21:48:46.859Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/65/682fe1f5d8aa08e9185a040f4432d01aa82a20c16f9e002456b8cbc2a99b/nglui-3.8.2-py3-none-any.whl", hash = "sha256:9865c0111e95850ae4f78479aa913075ddd4e979ffca0c4b1b8cddc4dd4ac6ee", size = 77358, upload-time = "2025-03-27T21:48:45.599Z" }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, +] + +[[package]] +name = "nvidia-ml-py" +version = "12.575.51" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/4d/6f017814ed5ac28e08e1b8a62e3a258957da27582c89b7f8f8b15ac3d2e7/nvidia_ml_py-12.575.51.tar.gz", hash = "sha256:6490e93fea99eb4e966327ae18c6eec6256194c921f23459c8767aee28c54581", size = 46597, upload-time = "2025-05-06T20:46:37.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/24/552ebea28f0570b9e65e62b50287a273804c9f997cc1c2dcd4e2d64b9e7d/nvidia_ml_py-12.575.51-py3-none-any.whl", hash = "sha256:eb8641800d98ce40a22f479873f34b482e214a7e80349c63be51c3919845446e", size = 47547, upload-time = "2025-05-06T20:46:36.457Z" }, +] + +[[package]] +name = "oauth2client" +version = "4.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httplib2" }, + { name = "pyasn1" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/7b/17244b1083e8e604bf154cf9b716aecd6388acd656dd01893d0d244c94d9/oauth2client-4.1.3.tar.gz", hash = "sha256:d486741e451287f69568a4d26d70d9acd73a2bbfa275746c535b4209891cccc6", size = 155910, upload-time = "2018-09-07T21:38:18.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/a9/4f25a14d23f0786b64875b91784607c2277eff25d48f915e39ff0cff505a/oauth2client-4.1.3-py2.py3-none-any.whl", hash = "sha256:b8a81cc5d60e2d364f0b1b98f958dbd472887acaf1a5b05e21c28c31a2d6d3ac", size = 98206, upload-time = "2018-09-07T21:38:16.742Z" }, +] + +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + +[[package]] +name = "ordered-set" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826, upload-time = "2022-01-26T14:38:56.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634, upload-time = "2022-01-26T14:38:48.677Z" }, +] + +[[package]] +name = "orderedmultidict" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/4e/3823a27d764bb8388711f4cb6f24e58453e92d6928f4163fdb01e3a3789f/orderedmultidict-1.0.1.tar.gz", hash = "sha256:04070bbb5e87291cc9bfa51df413677faf2141c73c61d2a5f7b26bea3cd882ad", size = 20706, upload-time = "2019-07-10T20:11:47.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/16/5e95c70bda8fe6ea715005c0db8e602400bdba50ae3c72cb380eba551289/orderedmultidict-1.0.1-py2.py3-none-any.whl", hash = "sha256:43c839a17ee3cdd62234c47deca1a8508a3f2ca1d0678a3bf791c87cf84adbf3", size = 11699, upload-time = "2019-07-10T20:11:45.622Z" }, +] + +[[package]] +name = "orjson" +version = "3.10.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/0b/fea456a3ffe74e70ba30e01ec183a9b26bec4d497f61dcfce1b601059c60/orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53", size = 5422810, upload-time = "2025-04-29T23:30:08.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/1a/67236da0916c1a192d5f4ccbe10ec495367a726996ceb7614eaa687112f2/orjson-3.10.18-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:50c15557afb7f6d63bc6d6348e0337a880a04eaa9cd7c9d569bcb4e760a24753", size = 249184, upload-time = "2025-04-29T23:28:53.612Z" }, + { url = "https://files.pythonhosted.org/packages/b3/bc/c7f1db3b1d094dc0c6c83ed16b161a16c214aaa77f311118a93f647b32dc/orjson-3.10.18-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:356b076f1662c9813d5fa56db7d63ccceef4c271b1fb3dd522aca291375fcf17", size = 133279, upload-time = "2025-04-29T23:28:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/af/84/664657cd14cc11f0d81e80e64766c7ba5c9b7fc1ec304117878cc1b4659c/orjson-3.10.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:559eb40a70a7494cd5beab2d73657262a74a2c59aff2068fdba8f0424ec5b39d", size = 136799, upload-time = "2025-04-29T23:28:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bb/f50039c5bb05a7ab024ed43ba25d0319e8722a0ac3babb0807e543349978/orjson-3.10.18-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3c29eb9a81e2fbc6fd7ddcfba3e101ba92eaff455b8d602bf7511088bbc0eae", size = 132791, upload-time = "2025-04-29T23:28:58.751Z" }, + { url = "https://files.pythonhosted.org/packages/93/8c/ee74709fc072c3ee219784173ddfe46f699598a1723d9d49cbc78d66df65/orjson-3.10.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6612787e5b0756a171c7d81ba245ef63a3533a637c335aa7fcb8e665f4a0966f", size = 137059, upload-time = "2025-04-29T23:29:00.129Z" }, + { url = "https://files.pythonhosted.org/packages/6a/37/e6d3109ee004296c80426b5a62b47bcadd96a3deab7443e56507823588c5/orjson-3.10.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ac6bd7be0dcab5b702c9d43d25e70eb456dfd2e119d512447468f6405b4a69c", size = 138359, upload-time = "2025-04-29T23:29:01.704Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5d/387dafae0e4691857c62bd02839a3bf3fa648eebd26185adfac58d09f207/orjson-3.10.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f72f100cee8dde70100406d5c1abba515a7df926d4ed81e20a9730c062fe9ad", size = 142853, upload-time = "2025-04-29T23:29:03.576Z" }, + { url = "https://files.pythonhosted.org/packages/27/6f/875e8e282105350b9a5341c0222a13419758545ae32ad6e0fcf5f64d76aa/orjson-3.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca85398d6d093dd41dc0983cbf54ab8e6afd1c547b6b8a311643917fbf4e0c", size = 133131, upload-time = "2025-04-29T23:29:05.753Z" }, + { url = "https://files.pythonhosted.org/packages/48/b2/73a1f0b4790dcb1e5a45f058f4f5dcadc8a85d90137b50d6bbc6afd0ae50/orjson-3.10.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22748de2a07fcc8781a70edb887abf801bb6142e6236123ff93d12d92db3d406", size = 134834, upload-time = "2025-04-29T23:29:07.35Z" }, + { url = "https://files.pythonhosted.org/packages/56/f5/7ed133a5525add9c14dbdf17d011dd82206ca6840811d32ac52a35935d19/orjson-3.10.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a83c9954a4107b9acd10291b7f12a6b29e35e8d43a414799906ea10e75438e6", size = 413368, upload-time = "2025-04-29T23:29:09.301Z" }, + { url = "https://files.pythonhosted.org/packages/11/7c/439654221ed9c3324bbac7bdf94cf06a971206b7b62327f11a52544e4982/orjson-3.10.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:303565c67a6c7b1f194c94632a4a39918e067bd6176a48bec697393865ce4f06", size = 153359, upload-time = "2025-04-29T23:29:10.813Z" }, + { url = "https://files.pythonhosted.org/packages/48/e7/d58074fa0cc9dd29a8fa2a6c8d5deebdfd82c6cfef72b0e4277c4017563a/orjson-3.10.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:86314fdb5053a2f5a5d881f03fca0219bfdf832912aa88d18676a5175c6916b5", size = 137466, upload-time = "2025-04-29T23:29:12.26Z" }, + { url = "https://files.pythonhosted.org/packages/57/4d/fe17581cf81fb70dfcef44e966aa4003360e4194d15a3f38cbffe873333a/orjson-3.10.18-cp312-cp312-win32.whl", hash = "sha256:187ec33bbec58c76dbd4066340067d9ece6e10067bb0cc074a21ae3300caa84e", size = 142683, upload-time = "2025-04-29T23:29:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/e6/22/469f62d25ab5f0f3aee256ea732e72dc3aab6d73bac777bd6277955bceef/orjson-3.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:f9f94cf6d3f9cd720d641f8399e390e7411487e493962213390d1ae45c7814fc", size = 134754, upload-time = "2025-04-29T23:29:15.338Z" }, + { url = "https://files.pythonhosted.org/packages/10/b0/1040c447fac5b91bc1e9c004b69ee50abb0c1ffd0d24406e1350c58a7fcb/orjson-3.10.18-cp312-cp312-win_arm64.whl", hash = "sha256:3d600be83fe4514944500fa8c2a0a77099025ec6482e8087d7659e891f23058a", size = 131218, upload-time = "2025-04-29T23:29:17.324Z" }, +] + +[[package]] +name = "osteoid" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastremap" }, + { name = "networkx" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/89/5dacd8724f8e85fd32b18fbaaf2c1532e66ea5d426f70a19a3597cf48d49/osteoid-0.3.2.tar.gz", hash = "sha256:c87c21e69bb1399875eefa8f9dd04d56cc8bcaa770af0fb0b2d9c225e0602de9", size = 22794, upload-time = "2025-05-02T16:03:04.94Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/f3/6c62e4e71ed3bad528d7bd2de17bda23e5721abd343938937cb1e484139d/osteoid-0.3.2-py3-none-any.whl", hash = "sha256:6a91c89bbfd1a4d24a3a909ae9544566d0f407f891f274ed16796ad023e9d65e", size = 23221, upload-time = "2025-05-02T16:03:02.807Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/51/48f713c4c728d7c55ef7444ba5ea027c26998d96d1a40953b346438602fc/pandas-2.3.0.tar.gz", hash = "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133", size = 4484490, upload-time = "2025-06-05T03:27:54.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/46/24192607058dd607dbfacdd060a2370f6afb19c2ccb617406469b9aeb8e7/pandas-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2eb4728a18dcd2908c7fccf74a982e241b467d178724545a48d0caf534b38ebf", size = 11573865, upload-time = "2025-06-05T03:26:46.774Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cc/ae8ea3b800757a70c9fdccc68b67dc0280a6e814efcf74e4211fd5dea1ca/pandas-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9d8c3187be7479ea5c3d30c32a5d73d62a621166675063b2edd21bc47614027", size = 10702154, upload-time = "2025-06-05T16:50:14.439Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ba/a7883d7aab3d24c6540a2768f679e7414582cc389876d469b40ec749d78b/pandas-2.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ff730713d4c4f2f1c860e36c005c7cefc1c7c80c21c0688fd605aa43c9fcf09", size = 11262180, upload-time = "2025-06-05T16:50:17.453Z" }, + { url = "https://files.pythonhosted.org/packages/01/a5/931fc3ad333d9d87b10107d948d757d67ebcfc33b1988d5faccc39c6845c/pandas-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba24af48643b12ffe49b27065d3babd52702d95ab70f50e1b34f71ca703e2c0d", size = 11991493, upload-time = "2025-06-05T03:26:51.813Z" }, + { url = "https://files.pythonhosted.org/packages/d7/bf/0213986830a92d44d55153c1d69b509431a972eb73f204242988c4e66e86/pandas-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:404d681c698e3c8a40a61d0cd9412cc7364ab9a9cc6e144ae2992e11a2e77a20", size = 12470733, upload-time = "2025-06-06T00:00:18.651Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0e/21eb48a3a34a7d4bac982afc2c4eb5ab09f2d988bdf29d92ba9ae8e90a79/pandas-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6021910b086b3ca756755e86ddc64e0ddafd5e58e076c72cb1585162e5ad259b", size = 13212406, upload-time = "2025-06-05T03:26:55.992Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d9/74017c4eec7a28892d8d6e31ae9de3baef71f5a5286e74e6b7aad7f8c837/pandas-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:094e271a15b579650ebf4c5155c05dcd2a14fd4fdd72cf4854b2f7ad31ea30be", size = 10976199, upload-time = "2025-06-05T03:26:59.594Z" }, +] + +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609, upload-time = "2024-04-05T09:43:55.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650, upload-time = "2024-04-05T09:43:53.299Z" }, +] + +[[package]] +name = "pathos" +version = "0.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, + { name = "multiprocess" }, + { name = "pox" }, + { name = "ppft" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/90/fdbe3bbfe79933db439e1844083cb6e9d5a9d3b686738549b3d22d06eae7/pathos-0.3.4.tar.gz", hash = "sha256:bad4912d0ef865654a7cc478da65f2e1d5b69f3d92c4a7d9c9845657783c0754", size = 167076, upload-time = "2025-04-17T03:37:08.234Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/0a/daece46e65c821d153746566a1604ac90338f0279b1fb858a3617eb60472/pathos-0.3.4-py3-none-any.whl", hash = "sha256:fe44883448c05c80d518b61df491b496f6190bb6860253f3254d8c9afb53c340", size = 82261, upload-time = "2025-04-17T03:37:06.936Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "sys_platform != 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "posix-ipc" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/2a/a57c6feeb9efc74d0c6867a915abf9dc00747e1789dc8d33c0ef5bf37906/posix_ipc-1.2.0.tar.gz", hash = "sha256:b7444e2703c156b3cb9fcb568e85d716232f3e78f04529ebc881cfb2aedb3838", size = 95859, upload-time = "2025-04-17T01:11:01.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/f2/ad7c69041909f21f2303d38a9c10520a01143bb3009c306857d25670bd39/posix_ipc-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:949f2bd61466b58ef4a2634c707ec0dbf77f76d79605815481ba295a02d24432", size = 11715, upload-time = "2025-04-17T01:09:52.889Z" }, + { url = "https://files.pythonhosted.org/packages/11/1b/1492f4568fcd433d07c4f930e4dc7c4a3840160f769d11af5f9b8e53d458/posix_ipc-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eb652ca855882bdf2b36c42031149e5eb28daad52490114501c73c9eb378be64", size = 12038, upload-time = "2025-04-17T01:09:54.082Z" }, + { url = "https://files.pythonhosted.org/packages/53/f2/93e5fcc326daa9428ffb63b2526faa9c5565caafe30c7385a28f7b9546db/posix_ipc-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875b121e649c49d405c2db0d737c7970822ef70c3b0d63a66a95a94f8672caf7", size = 50227, upload-time = "2025-04-17T01:09:55.321Z" }, + { url = "https://files.pythonhosted.org/packages/49/66/660c6076bf12b3070a22be98d1089e0439060b09f089165affbd0ab8d593/posix_ipc-1.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eabc30bf5c6547dffb4ed054a8eb62f8903829d140ded116766c93afc841830c", size = 46531, upload-time = "2025-04-17T01:09:56.235Z" }, + { url = "https://files.pythonhosted.org/packages/91/1d/72453d8d914620ad18d79594ac34ea45e67cfa4b8141d1551170a1bd5286/posix_ipc-1.2.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3eac22f0b9d31028037f7f2e569bef5874d99213de165944dcaaa71514692cd", size = 51164, upload-time = "2025-04-17T01:09:57.162Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f0/a687146e2605799e21efa42f93b3e825d69ff6193bd76ae18c99a5735a57/posix_ipc-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea3a33d5a878ef3ff33284cf1a3d97ee3ae8c75339a4f76db20df8f2453f84b6", size = 49183, upload-time = "2025-04-17T01:09:58.011Z" }, + { url = "https://files.pythonhosted.org/packages/cd/61/80d5dd000af071b4b77310958f58b3791257794ea400b61785aaa0514c7d/posix_ipc-1.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5fb7a8b4ed016a3deada828d8c2a4ec0bf0f36a984bf45ede2356cfc78d5c398", size = 46293, upload-time = "2025-04-17T01:09:58.857Z" }, + { url = "https://files.pythonhosted.org/packages/b0/15/bc70e228352aedd4293ddfcf7f83ed453ef2cc68430ed8a46608bd37f244/posix_ipc-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:65ce8371899ce170388eed7579f4072ac42345824198bf5d4eda5e84b8bdf123", size = 49465, upload-time = "2025-04-17T01:10:00.129Z" }, +] + +[[package]] +name = "pox" +version = "0.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/99/42670d273fd598a6fe98c8b2f593ee425b29e44f2d1a61ff622031204ccd/pox-0.3.6.tar.gz", hash = "sha256:84eeed39600159a62804aacfc00e353edeaae67d8c647ccaaab73a6efed3f605", size = 119393, upload-time = "2025-04-16T00:05:49.811Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/c2/6435789c26661bef699868ee54d2763aea636a1ed21ec8e350b1f9f65888/pox-0.3.6-py3-none-any.whl", hash = "sha256:d48654d0a3dca0c9c02dccae54a53c3870286a5217ad306b2bd94f84e008bc1b", size = 29495, upload-time = "2025-04-16T00:05:48.319Z" }, +] + +[[package]] +name = "ppft" +version = "1.7.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/46/9e9f2ae7e8e284acbde6ab36f7f4a35b273519a60c0ed419af2da780d49f/ppft-1.7.7.tar.gz", hash = "sha256:f3f77448cfe24c2b8d2296b6d8732280b25041a3f3e1f551856c6451d3e01b96", size = 136272, upload-time = "2025-04-16T01:47:40.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/23/6aef7c24f4ee6f765aeaaaa3bf24cfdb0730a20336a02b1a061d227d84be/ppft-1.7.7-py3-none-any.whl", hash = "sha256:fb7524db110682de886b4bb5b08f7bf6a38940566074ef2f62521cbbd3864676", size = 56764, upload-time = "2025-04-16T01:47:39.453Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, +] + +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, + { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, + { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, + { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, + { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, + { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, + { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +] + +[[package]] +name = "proto-plus" +version = "1.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142, upload-time = "2025-03-10T15:54:38.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163, upload-time = "2025-03-10T15:54:37.335Z" }, +] + +[[package]] +name = "protobuf" +version = "6.31.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/f3/b9655a711b32c19720253f6f06326faf90580834e2e83f840472d752bc8b/protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a", size = 441797, upload-time = "2025-05-28T19:25:54.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/6f/6ab8e4bf962fd5570d3deaa2d5c38f0a363f57b4501047b5ebeb83ab1125/protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9", size = 423603, upload-time = "2025-05-28T19:25:41.198Z" }, + { url = "https://files.pythonhosted.org/packages/44/3a/b15c4347dd4bf3a1b0ee882f384623e2063bb5cf9fa9d57990a4f7df2fb6/protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447", size = 435283, upload-time = "2025-05-28T19:25:44.275Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c9/b9689a2a250264a84e66c46d8862ba788ee7a641cdca39bccf64f59284b7/protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402", size = 425604, upload-time = "2025-05-28T19:25:45.702Z" }, + { url = "https://files.pythonhosted.org/packages/76/a1/7a5a94032c83375e4fe7e7f56e3976ea6ac90c5e85fac8576409e25c39c3/protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39", size = 322115, upload-time = "2025-05-28T19:25:47.128Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/b59d405d64d31999244643d88c45c8241c58f17cc887e73bcb90602327f8/protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6", size = 321070, upload-time = "2025-05-28T19:25:50.036Z" }, + { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771, upload-time = "2024-10-16T11:20:35.234Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336, upload-time = "2024-10-16T11:20:38.742Z" }, + { url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637, upload-time = "2024-10-16T11:20:42.145Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", size = 3082097, upload-time = "2024-10-16T11:20:46.185Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", size = 3264776, upload-time = "2024-10-16T11:20:50.879Z" }, + { url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", size = 3020968, upload-time = "2024-10-16T11:20:56.819Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", size = 2872334, upload-time = "2024-10-16T11:21:02.411Z" }, + { url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", size = 2822722, upload-time = "2024-10-16T11:21:09.01Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", size = 2920132, upload-time = "2024-10-16T11:21:16.339Z" }, + { url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", size = 2959312, upload-time = "2024-10-16T11:21:25.584Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", size = 1025191, upload-time = "2024-10-16T11:21:29.912Z" }, + { url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031, upload-time = "2024-10-16T11:21:34.211Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pyarrow" +version = "20.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/ee/a7810cb9f3d6e9238e61d312076a9859bf3668fd21c69744de9532383912/pyarrow-20.0.0.tar.gz", hash = "sha256:febc4a913592573c8d5805091a6c2b5064c8bd6e002131f01061797d91c783c1", size = 1125187, upload-time = "2025-04-27T12:34:23.264Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/d6/0c10e0d54f6c13eb464ee9b67a68b8c71bcf2f67760ef5b6fbcddd2ab05f/pyarrow-20.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:75a51a5b0eef32727a247707d4755322cb970be7e935172b6a3a9f9ae98404ba", size = 30815067, upload-time = "2025-04-27T12:29:44.384Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e2/04e9874abe4094a06fd8b0cbb0f1312d8dd7d707f144c2ec1e5e8f452ffa/pyarrow-20.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:211d5e84cecc640c7a3ab900f930aaff5cd2702177e0d562d426fb7c4f737781", size = 32297128, upload-time = "2025-04-27T12:29:52.038Z" }, + { url = "https://files.pythonhosted.org/packages/31/fd/c565e5dcc906a3b471a83273039cb75cb79aad4a2d4a12f76cc5ae90a4b8/pyarrow-20.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ba3cf4182828be7a896cbd232aa8dd6a31bd1f9e32776cc3796c012855e1199", size = 41334890, upload-time = "2025-04-27T12:29:59.452Z" }, + { url = "https://files.pythonhosted.org/packages/af/a9/3bdd799e2c9b20c1ea6dc6fa8e83f29480a97711cf806e823f808c2316ac/pyarrow-20.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c3a01f313ffe27ac4126f4c2e5ea0f36a5fc6ab51f8726cf41fee4b256680bd", size = 42421775, upload-time = "2025-04-27T12:30:06.875Z" }, + { url = "https://files.pythonhosted.org/packages/10/f7/da98ccd86354c332f593218101ae56568d5dcedb460e342000bd89c49cc1/pyarrow-20.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a2791f69ad72addd33510fec7bb14ee06c2a448e06b649e264c094c5b5f7ce28", size = 40687231, upload-time = "2025-04-27T12:30:13.954Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1b/2168d6050e52ff1e6cefc61d600723870bf569cbf41d13db939c8cf97a16/pyarrow-20.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4250e28a22302ce8692d3a0e8ec9d9dde54ec00d237cff4dfa9c1fbf79e472a8", size = 42295639, upload-time = "2025-04-27T12:30:21.949Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/2d976c0c7158fd25591c8ca55aee026e6d5745a021915a1835578707feb3/pyarrow-20.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:89e030dc58fc760e4010148e6ff164d2f44441490280ef1e97a542375e41058e", size = 42908549, upload-time = "2025-04-27T12:30:29.551Z" }, + { url = "https://files.pythonhosted.org/packages/31/a9/dfb999c2fc6911201dcbf348247f9cc382a8990f9ab45c12eabfd7243a38/pyarrow-20.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6102b4864d77102dbbb72965618e204e550135a940c2534711d5ffa787df2a5a", size = 44557216, upload-time = "2025-04-27T12:30:36.977Z" }, + { url = "https://files.pythonhosted.org/packages/a0/8e/9adee63dfa3911be2382fb4d92e4b2e7d82610f9d9f668493bebaa2af50f/pyarrow-20.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:96d6a0a37d9c98be08f5ed6a10831d88d52cac7b13f5287f1e0f625a0de8062b", size = 25660496, upload-time = "2025-04-27T12:30:42.809Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyinstrument" +version = "5.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/a5/f11adb20528285ab85878f8fe1a22d5759c68c49ad3b545d1c1808ab52da/pyinstrument-5.0.3.tar.gz", hash = "sha256:88281dfe65e5d6b42035bba72808cbcd4cb46cd0a0ba35da23d3e74a41ebdd05", size = 264026, upload-time = "2025-07-02T14:14:03.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/68/c7c2429dffc1dceaaa93d003a37fa00beff06285bf0f15551b9a053e2a93/pyinstrument-5.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c6176f037cb4c673d0f121cb4117b1366aac3d80e451a3d3af84ba2b145194fc", size = 129579, upload-time = "2025-07-02T14:13:17.735Z" }, + { url = "https://files.pythonhosted.org/packages/1e/65/dfcf67493e2513e7541b3e7a9d67741af1fdbb0a9cd621945b83c10a4a4e/pyinstrument-5.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:df8b02262208a1310a11f0c037e4efeb8d628660be60e3c9917d9ff950fa1519", size = 122126, upload-time = "2025-07-02T14:13:18.878Z" }, + { url = "https://files.pythonhosted.org/packages/9c/66/1bfbb59ffeb4864f6d4f8fd701d9986ddbf66aa940cf8459eca6f32864f5/pyinstrument-5.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df477beeb37ba35b7d1f3cbefc973d3cc09a9281195ac18d72d4c92f8916c323", size = 146733, upload-time = "2025-07-02T14:13:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/6b/72/4f20451833fb4ad37d6ab5bd076e0e06778315f33f24e2320be283a9ab45/pyinstrument-5.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d54799accccb2a8611d0975ff696e20c775af55d4ed2f8e0e07806bb5db5b015", size = 145675, upload-time = "2025-07-02T14:13:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/96/d5/15bf3832ebc3172ebe7b0f29dadeaa65802b511fe95fa1acad0314a7a3dc/pyinstrument-5.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97eaa3bbe181903ccf955ade86d31aca7805d3bc06f5e742d767845005a3ca75", size = 145715, upload-time = "2025-07-02T14:13:22.79Z" }, + { url = "https://files.pythonhosted.org/packages/18/13/b9754a10267573bca108c645dbe1cc5ada3aa0524987ec2bf84fe3a2e30c/pyinstrument-5.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2ecfff795dce1fcbedef4f6a63cd2ae549688fb1b6fbc8fa16d852d70da3da80", size = 145499, upload-time = "2025-07-02T14:13:24.296Z" }, + { url = "https://files.pythonhosted.org/packages/2c/85/aa5bc8ecf93f1fb30a69e90f6c5c53db0e3772b2630f58042fa32aaa27bb/pyinstrument-5.0.3-cp312-cp312-win32.whl", hash = "sha256:9b513ff9960f131bf1ab46034315146b825ccd7d6f84680f2a3642b24abe7f3c", size = 123546, upload-time = "2025-07-02T14:13:25.584Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7f/113b16d55e8d2dd9143628eec39b138fd6c52f72dcd11b4dae4a3845da4d/pyinstrument-5.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:88df7e3ab11604ae7cef1f576c097a08752bf8fc13c5755803bd3cd92f15aba3", size = 124314, upload-time = "2025-07-02T14:13:26.708Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825, upload-time = "2024-08-01T15:01:08.445Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344, upload-time = "2024-08-01T15:01:06.481Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "10.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/0a/c06b542ac108bfc73200677309cd9188a3a01b127a63f20cadc18d873d88/pymdown_extensions-10.16.tar.gz", hash = "sha256:71dac4fca63fabeffd3eb9038b756161a33ec6e8d230853d3cecf562155ab3de", size = 853197, upload-time = "2025-06-21T17:56:36.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/d4/10bb14004d3c792811e05e21b5e5dcae805aacb739bd12a0540967b99592/pymdown_extensions-10.16-py3-none-any.whl", hash = "sha256:f5dd064a4db588cb2d95229fc4ee63a1b16cc8b4d0e6145c0899ed8723da1df2", size = 266143, upload-time = "2025-06-21T17:56:35.356Z" }, +] + +[[package]] +name = "pyopenssl" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/8c/cd89ad05804f8e3c17dea8f178c3f40eeab5694c30e0c9f5bcd49f576fc3/pyopenssl-25.1.0.tar.gz", hash = "sha256:8d031884482e0c67ee92bf9a4d8cceb08d92aba7136432ffb0703c5280fc205b", size = 179937, upload-time = "2025-05-17T16:28:31.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/28/2659c02301b9500751f8d42f9a6632e1508aa5120de5e43042b8b30f8d5d/pyopenssl-25.1.0-py3-none-any.whl", hash = "sha256:2b11f239acc47ac2e5aca04fd7fa829800aeee22a2eb30d744572a157bd8a1ab", size = 56771, upload-time = "2025-05-17T16:28:29.197Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, +] + +[[package]] +name = "pyrsistent" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/3a/5031723c09068e9c8c2f0bc25c3a9245f2b1d1aea8396c787a408f2b95ca/pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4", size = 103642, upload-time = "2023-10-25T21:06:56.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/ee/ff2ed52032ac1ce2e7ba19e79bd5b05d152ebfb77956cf08fcd6e8d760ea/pyrsistent-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:09848306523a3aba463c4b49493a760e7a6ca52e4826aa100ee99d8d39b7ad1e", size = 83537, upload-time = "2023-10-25T21:06:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/80/f1/338d0050b24c3132bcfc79b68c3a5f54bce3d213ecef74d37e988b971d8a/pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a14798c3005ec892bbada26485c2eea3b54109cb2533713e355c806891f63c5e", size = 122615, upload-time = "2023-10-25T21:06:25.815Z" }, + { url = "https://files.pythonhosted.org/packages/07/3a/e56d6431b713518094fae6ff833a04a6f49ad0fbe25fb7c0dc7408e19d20/pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b14decb628fac50db5e02ee5a35a9c0772d20277824cfe845c8a8b717c15daa3", size = 122335, upload-time = "2023-10-25T21:06:28.631Z" }, + { url = "https://files.pythonhosted.org/packages/4a/bb/5f40a4d5e985a43b43f607250e766cdec28904682c3505eb0bd343a4b7db/pyrsistent-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e2c116cc804d9b09ce9814d17df5edf1df0c624aba3b43bc1ad90411487036d", size = 118510, upload-time = "2023-10-25T21:06:30.718Z" }, + { url = "https://files.pythonhosted.org/packages/1c/13/e6a22f40f5800af116c02c28e29f15c06aa41cb2036f6a64ab124647f28b/pyrsistent-0.20.0-cp312-cp312-win32.whl", hash = "sha256:e78d0c7c1e99a4a45c99143900ea0546025e41bb59ebc10182e947cf1ece9174", size = 60865, upload-time = "2023-10-25T21:06:32.742Z" }, + { url = "https://files.pythonhosted.org/packages/75/ef/2fa3b55023ec07c22682c957808f9a41836da4cd006b5f55ec76bf0fbfa6/pyrsistent-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:4021a7f963d88ccd15b523787d18ed5e5269ce57aa4037146a2377ff607ae87d", size = 63239, upload-time = "2023-10-25T21:06:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/23/88/0acd180010aaed4987c85700b7cc17f9505f3edb4e5873e4dc67f613e338/pyrsistent-0.20.0-py3-none-any.whl", hash = "sha256:c55acc4733aad6560a7f5f818466631f07efc001fd023f34a6c203f8b6df0f0b", size = 58106, upload-time = "2023-10-25T21:06:54.387Z" }, +] + +[[package]] +name = "pysimdjson" +version = "7.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/24/65e3cad88e74ef8ca59fefded953eb78ebface8a3199c3a97fe318a7387b/pysimdjson-7.0.2.tar.gz", hash = "sha256:44cf276e48912a3b9c7ca362c14da8420a7ac15a9f1a16ec95becff86db3904a", size = 1397812, upload-time = "2025-06-28T20:37:24.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/81/2a7bee8961e9519084ee290bb7135844f1f786ec8a26f62d48e7fd23a08b/pysimdjson-7.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ea5ffbdfde6a26b05bec12263ffacf8435d2e51c3793b44aa090fb38e709434", size = 1877768, upload-time = "2025-06-28T20:36:38.463Z" }, + { url = "https://files.pythonhosted.org/packages/b3/55/dfa21b647ff1a54e5925664ebfe3f1f800375546f0665347f3041a52bf5a/pysimdjson-7.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4fbe295c84bd9406ac8fc38ab76a6ff1187df11be9348e5937f9dcc42f41c8f8", size = 1656024, upload-time = "2025-06-28T20:36:39.847Z" }, + { url = "https://files.pythonhosted.org/packages/64/bd/06b744b0b33f4932ad4ed51fdb8ec5eeca6f7980ad502839dbfbe5ac60c9/pysimdjson-7.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abbbd51ef301083c9ee885d1ba8d3c2081c462d56c2d0e2f603cc917a44f7ed5", size = 2771741, upload-time = "2025-06-28T20:36:41.249Z" }, + { url = "https://files.pythonhosted.org/packages/90/a4/c13afff7d4cd2fd001508f0d411063a8a9c451d694178b5230d50c8caf98/pysimdjson-7.0.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14ca76010e5d82f4c0de90586a940e57c28beee937b4a53ef239b88ebee7190e", size = 2823997, upload-time = "2025-06-28T20:36:42.704Z" }, + { url = "https://files.pythonhosted.org/packages/58/da/459c89f3dbb8344f6b2a374850d13522cc9a89726faea4319568034f1f1f/pysimdjson-7.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1de838fc7aa473db24ddacc0b285928bd74d5830755f8471b17c34e78e94840", size = 3248858, upload-time = "2025-06-28T20:36:43.969Z" }, + { url = "https://files.pythonhosted.org/packages/d6/90/c9274cb68412b2b119a0d72c71d57b01f05397b59afc7cec9ff0b28a88d5/pysimdjson-7.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:061259784a9a4746d40a3a3f20542a19bd0e403e49af4aa3bd9a1626429ce704", size = 2529651, upload-time = "2025-06-28T20:36:45.266Z" }, + { url = "https://files.pythonhosted.org/packages/95/3b/8f3a3866daa6776ea3d3986b0c21cc678bd0bb5872a19a18170fae396e90/pysimdjson-7.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:27c2e4cde872b8d3a05dc855341508d11d056bb3b25eddbc17e533417a848a52", size = 3664874, upload-time = "2025-06-28T20:36:46.541Z" }, + { url = "https://files.pythonhosted.org/packages/1e/21/376e54868918d8b4831fb8653c1976615f99a11d95e0502ecaaa7a306d32/pysimdjson-7.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:41a18886861d47b63ef6231796a30ccc547bf3772a06fa60b681ee8f00a614ce", size = 3579057, upload-time = "2025-06-28T20:36:47.843Z" }, + { url = "https://files.pythonhosted.org/packages/5f/92/29bf4549ec6d692aca1cc11b1ff8a8bf8f742dd09e834f649e2567eb1438/pysimdjson-7.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fdbd392590613ddbc4922ab5374282dddefa94471fc7a97bc2c1df6a450dd671", size = 3818097, upload-time = "2025-06-28T20:36:49.319Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f8/ff0a6e3ee124eef780f164c95ea95ccca1ac04e4cff483e728aa029e7b36/pysimdjson-7.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb217ddaedd5f28ca7db16e4ea972f02c6db380827ec312c7e6a9371ca5e4d7c", size = 4201879, upload-time = "2025-06-28T20:36:50.801Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b0/7f60a32fef8b97407f07c80d367fb161c9245bd3c1de1597c9f4cb1c6536/pysimdjson-7.0.2-cp312-cp312-win32.whl", hash = "sha256:bf5af81e19b0cef57679523759f9219e2641e5156a4ee5b854e49e3e6b1690ab", size = 1529773, upload-time = "2025-06-28T20:36:51.97Z" }, + { url = "https://files.pythonhosted.org/packages/28/e7/b127c677f6aa8991ba6f9ea99a08aa167ab93a1844f6da35c65fa4b98179/pysimdjson-7.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:782ee03679eaea5b28d9bc9279bc0f0f03d251c17571396f3ed50ba86023d88f", size = 1574523, upload-time = "2025-06-28T20:36:53.103Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, +] + +[[package]] +name = "pytest-env" +version = "1.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/31/27f28431a16b83cab7a636dce59cf397517807d247caa38ee67d65e71ef8/pytest_env-1.1.5.tar.gz", hash = "sha256:91209840aa0e43385073ac464a554ad2947cc2fd663a9debf88d03b01e0cc1cf", size = 8911, upload-time = "2024-09-17T22:39:18.566Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/b8/87cfb16045c9d4092cfcf526135d73b88101aac83bc1adcf82dfb5fd3833/pytest_env-1.1.5-py3-none-any.whl", hash = "sha256:ce90cf8772878515c24b31cd97c7fa1f4481cd68d588419fd45f10ecaee6bc30", size = 6141, upload-time = "2024-09-17T22:39:16.942Z" }, +] + +[[package]] +name = "python-box" +version = "7.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/f7/635eed8c500adf26208e86e985bbffb6ff039cd8950e3a4749ceca904218/python_box-7.3.2.tar.gz", hash = "sha256:028b9917129e67f311932d93347b8a4f1b500d7a5a2870ee3c035f4e7b19403b", size = 45771, upload-time = "2025-01-16T19:10:05.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/68/0c2f289d8055d3e1b156ff258847f0e8f1010063e284cf5a612f09435575/python_box-7.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39009a2da5c20133718b24891a206592adbe09169856aedc450ad1600fc2e511", size = 1819681, upload-time = "2025-01-16T19:10:25.187Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/76b4d6d0e41edb676a229f032848a1ecea166890fa8d501513ea1a030f4d/python_box-7.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2a72e2f6fb97c7e472ff3272da207ecc615aa222e52e98352391428527c469", size = 4270424, upload-time = "2025-01-16T19:15:32.376Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6b/32484b2a3cd2fb5e5f56bfb53a4537d93a4d2014ccf7fc0c0017fa6f65e9/python_box-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9eead914b9fb7d98a1473f5027dcfe27d26b3a10ffa33b9ba22cf948a23cd280", size = 1211252, upload-time = "2025-01-16T19:11:00.248Z" }, + { url = "https://files.pythonhosted.org/packages/37/13/8a990c6e2b6cc12700dce16f3cb383324e6d9a30f604eca22a2fdf84c923/python_box-7.3.2-py3-none-any.whl", hash = "sha256:fd7d74d5a848623f93b5221fd9fb00b8c00ff0e130fa87f396277aa188659c92", size = 29479, upload-time = "2025-01-16T19:10:02.749Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-jsonschema-objects" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "inflection" }, + { name = "jsonschema" }, + { name = "markdown" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/2d/e76f262a047b425062a3fa57d4163995d74c866ef999584ed1c367a70c80/python_jsonschema_objects-0.4.6.tar.gz", hash = "sha256:7d4405d7346362d18be61058fde20b7b3d9c1dbb1104904d2e907d14de6a34b2", size = 67994, upload-time = "2023-09-15T14:24:03.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/f3/315deda6c45e7ce44407df0d71b6ff6c25ab92de12d54462c905a7423736/python_jsonschema_objects-0.4.6-py2.py3-none-any.whl", hash = "sha256:918ab5dd413499cba2441c7bfe8ae07d2f159aa8199aa09345287677a57b3fb6", size = 28241, upload-time = "2023-09-15T14:24:01.735Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pywin32" +version = "310" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239, upload-time = "2025-03-17T00:55:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839, upload-time = "2025-03-17T00:56:00.8Z" }, + { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470, upload-time = "2025-03-17T00:56:02.601Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, +] + +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/06/50a4e9648b3e8b992bef8eb632e457307553a89d294103213cfd47b3da69/pyzmq-27.0.0.tar.gz", hash = "sha256:b1f08eeb9ce1510e6939b6e5dcd46a17765e2333daae78ecf4606808442e52cf", size = 280478, upload-time = "2025-06-13T14:09:07.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/a7/9ad68f55b8834ede477842214feba6a4c786d936c022a67625497aacf61d/pyzmq-27.0.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:cbabc59dcfaac66655c040dfcb8118f133fb5dde185e5fc152628354c1598e52", size = 1305438, upload-time = "2025-06-13T14:07:31.676Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ee/26aa0f98665a22bc90ebe12dced1de5f3eaca05363b717f6fb229b3421b3/pyzmq-27.0.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:cb0ac5179cba4b2f94f1aa208fbb77b62c4c9bf24dd446278b8b602cf85fcda3", size = 895095, upload-time = "2025-06-13T14:07:33.104Z" }, + { url = "https://files.pythonhosted.org/packages/cf/85/c57e7ab216ecd8aa4cc7e3b83b06cc4e9cf45c87b0afc095f10cd5ce87c1/pyzmq-27.0.0-cp312-abi3-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53a48f0228eab6cbf69fde3aa3c03cbe04e50e623ef92ae395fce47ef8a76152", size = 651826, upload-time = "2025-06-13T14:07:34.831Z" }, + { url = "https://files.pythonhosted.org/packages/69/9a/9ea7e230feda9400fb0ae0d61d7d6ddda635e718d941c44eeab22a179d34/pyzmq-27.0.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:111db5f395e09f7e775f759d598f43cb815fc58e0147623c4816486e1a39dc22", size = 839750, upload-time = "2025-06-13T14:07:36.553Z" }, + { url = "https://files.pythonhosted.org/packages/08/66/4cebfbe71f3dfbd417011daca267539f62ed0fbc68105357b68bbb1a25b7/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c8878011653dcdc27cc2c57e04ff96f0471e797f5c19ac3d7813a245bcb24371", size = 1641357, upload-time = "2025-06-13T14:07:38.21Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f6/b0f62578c08d2471c791287149cb8c2aaea414ae98c6e995c7dbe008adfb/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:c0ed2c1f335ba55b5fdc964622254917d6b782311c50e138863eda409fbb3b6d", size = 2020281, upload-time = "2025-06-13T14:07:39.599Z" }, + { url = "https://files.pythonhosted.org/packages/37/b9/4f670b15c7498495da9159edc374ec09c88a86d9cd5a47d892f69df23450/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e918d70862d4cfd4b1c187310015646a14e1f5917922ab45b29f28f345eeb6be", size = 1877110, upload-time = "2025-06-13T14:07:41.027Z" }, + { url = "https://files.pythonhosted.org/packages/66/31/9dee25c226295b740609f0d46db2fe972b23b6f5cf786360980524a3ba92/pyzmq-27.0.0-cp312-abi3-win32.whl", hash = "sha256:88b4e43cab04c3c0f0d55df3b1eef62df2b629a1a369b5289a58f6fa8b07c4f4", size = 559297, upload-time = "2025-06-13T14:07:42.533Z" }, + { url = "https://files.pythonhosted.org/packages/9b/12/52da5509800f7ff2d287b2f2b4e636e7ea0f001181cba6964ff6c1537778/pyzmq-27.0.0-cp312-abi3-win_amd64.whl", hash = "sha256:dce4199bf5f648a902ce37e7b3afa286f305cd2ef7a8b6ec907470ccb6c8b371", size = 619203, upload-time = "2025-06-13T14:07:43.843Z" }, + { url = "https://files.pythonhosted.org/packages/93/6d/7f2e53b19d1edb1eb4f09ec7c3a1f945ca0aac272099eab757d15699202b/pyzmq-27.0.0-cp312-abi3-win_arm64.whl", hash = "sha256:56e46bbb85d52c1072b3f809cc1ce77251d560bc036d3a312b96db1afe76db2e", size = 551927, upload-time = "2025-06-13T14:07:45.51Z" }, +] + +[[package]] +name = "questionary" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/b8/d16eb579277f3de9e56e5ad25280fab52fc5774117fb70362e8c2e016559/questionary-2.1.0.tar.gz", hash = "sha256:6302cdd645b19667d8f6e6634774e9538bfcd1aad9be287e743d96cacaf95587", size = 26775, upload-time = "2024-12-29T11:49:17.802Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/3f/11dd4cd4f39e05128bfd20138faea57bec56f9ffba6185d276e3107ba5b2/questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec", size = 36747, upload-time = "2024-12-29T11:49:16.734Z" }, +] + +[[package]] +name = "redis" +version = "5.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyjwt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/dd/2b37032f4119dff2a2f9bbcaade03221b100ba26051bb96e275de3e5db7a/redis-5.3.0.tar.gz", hash = "sha256:8d69d2dde11a12dc85d0dbf5c45577a5af048e2456f7077d87ad35c1c81c310e", size = 4626288, upload-time = "2025-04-30T14:54:40.634Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/b0/aa601efe12180ba492b02e270554877e68467e66bda5d73e51eaa8ecc78a/redis-5.3.0-py3-none-any.whl", hash = "sha256:f1deeca1ea2ef25c1e4e46b07f4ea1275140526b1feea4c6459c0ec27a10ef83", size = 272836, upload-time = "2025-04-30T14:54:30.744Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + +[[package]] +name = "rich" +version = "13.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149, upload-time = "2024-11-01T16:43:57.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424, upload-time = "2024-11-01T16:43:55.817Z" }, +] + +[[package]] +name = "rich-click" +version = "1.8.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/a8/dcc0a8ec9e91d76ecad9413a84b6d3a3310c6111cfe012d75ed385c78d96/rich_click-1.8.9.tar.gz", hash = "sha256:fd98c0ab9ddc1cf9c0b7463f68daf28b4d0033a74214ceb02f761b3ff2af3136", size = 39378, upload-time = "2025-05-19T21:33:05.569Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/c2/9fce4c8a9587c4e90500114d742fe8ef0fd92d7bad29d136bb9941add271/rich_click-1.8.9-py3-none-any.whl", hash = "sha256:c3fa81ed8a671a10de65a9e20abf642cfdac6fdb882db1ef465ee33919fbcfe2", size = 36082, upload-time = "2025-05-19T21:33:04.195Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "ruff" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/97/38/796a101608a90494440856ccfb52b1edae90de0b817e76bfade66b12d320/ruff-0.12.1.tar.gz", hash = "sha256:806bbc17f1104fd57451a98a58df35388ee3ab422e029e8f5cf30aa4af2c138c", size = 4413426, upload-time = "2025-06-26T20:34:14.784Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/bf/3dba52c1d12ab5e78d75bd78ad52fb85a6a1f29cc447c2423037b82bed0d/ruff-0.12.1-py3-none-linux_armv6l.whl", hash = "sha256:6013a46d865111e2edb71ad692fbb8262e6c172587a57c0669332a449384a36b", size = 10305649, upload-time = "2025-06-26T20:33:39.242Z" }, + { url = "https://files.pythonhosted.org/packages/8c/65/dab1ba90269bc8c81ce1d499a6517e28fe6f87b2119ec449257d0983cceb/ruff-0.12.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b3f75a19e03a4b0757d1412edb7f27cffb0c700365e9d6b60bc1b68d35bc89e0", size = 11120201, upload-time = "2025-06-26T20:33:42.207Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3e/2d819ffda01defe857fa2dd4cba4d19109713df4034cc36f06bbf582d62a/ruff-0.12.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9a256522893cb7e92bb1e1153283927f842dea2e48619c803243dccc8437b8be", size = 10466769, upload-time = "2025-06-26T20:33:44.102Z" }, + { url = "https://files.pythonhosted.org/packages/63/37/bde4cf84dbd7821c8de56ec4ccc2816bce8125684f7b9e22fe4ad92364de/ruff-0.12.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:069052605fe74c765a5b4272eb89880e0ff7a31e6c0dbf8767203c1fbd31c7ff", size = 10660902, upload-time = "2025-06-26T20:33:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/0e/3a/390782a9ed1358c95e78ccc745eed1a9d657a537e5c4c4812fce06c8d1a0/ruff-0.12.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a684f125a4fec2d5a6501a466be3841113ba6847827be4573fddf8308b83477d", size = 10167002, upload-time = "2025-06-26T20:33:47.81Z" }, + { url = "https://files.pythonhosted.org/packages/6d/05/f2d4c965009634830e97ffe733201ec59e4addc5b1c0efa035645baa9e5f/ruff-0.12.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdecdef753bf1e95797593007569d8e1697a54fca843d78f6862f7dc279e23bd", size = 11751522, upload-time = "2025-06-26T20:33:49.857Z" }, + { url = "https://files.pythonhosted.org/packages/35/4e/4bfc519b5fcd462233f82fc20ef8b1e5ecce476c283b355af92c0935d5d9/ruff-0.12.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:70d52a058c0e7b88b602f575d23596e89bd7d8196437a4148381a3f73fcd5010", size = 12520264, upload-time = "2025-06-26T20:33:52.199Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/7756a6925da236b3a31f234b4167397c3e5f91edb861028a631546bad719/ruff-0.12.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84d0a69d1e8d716dfeab22d8d5e7c786b73f2106429a933cee51d7b09f861d4e", size = 12133882, upload-time = "2025-06-26T20:33:54.231Z" }, + { url = "https://files.pythonhosted.org/packages/dd/00/40da9c66d4a4d51291e619be6757fa65c91b92456ff4f01101593f3a1170/ruff-0.12.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cc32e863adcf9e71690248607ccdf25252eeeab5193768e6873b901fd441fed", size = 11608941, upload-time = "2025-06-26T20:33:56.202Z" }, + { url = "https://files.pythonhosted.org/packages/91/e7/f898391cc026a77fbe68dfea5940f8213622474cb848eb30215538a2dadf/ruff-0.12.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd49a4619f90d5afc65cf42e07b6ae98bb454fd5029d03b306bd9e2273d44cc", size = 11602887, upload-time = "2025-06-26T20:33:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/f6/02/0891872fc6aab8678084f4cf8826f85c5d2d24aa9114092139a38123f94b/ruff-0.12.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ed5af6aaaea20710e77698e2055b9ff9b3494891e1b24d26c07055459bb717e9", size = 10521742, upload-time = "2025-06-26T20:34:00.465Z" }, + { url = "https://files.pythonhosted.org/packages/2a/98/d6534322c74a7d47b0f33b036b2498ccac99d8d8c40edadb552c038cecf1/ruff-0.12.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:801d626de15e6bf988fbe7ce59b303a914ff9c616d5866f8c79eb5012720ae13", size = 10149909, upload-time = "2025-06-26T20:34:02.603Z" }, + { url = "https://files.pythonhosted.org/packages/34/5c/9b7ba8c19a31e2b6bd5e31aa1e65b533208a30512f118805371dbbbdf6a9/ruff-0.12.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2be9d32a147f98a1972c1e4df9a6956d612ca5f5578536814372113d09a27a6c", size = 11136005, upload-time = "2025-06-26T20:34:04.723Z" }, + { url = "https://files.pythonhosted.org/packages/dc/34/9bbefa4d0ff2c000e4e533f591499f6b834346025e11da97f4ded21cb23e/ruff-0.12.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:49b7ce354eed2a322fbaea80168c902de9504e6e174fd501e9447cad0232f9e6", size = 11648579, upload-time = "2025-06-26T20:34:06.766Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1c/20cdb593783f8f411839ce749ec9ae9e4298c2b2079b40295c3e6e2089e1/ruff-0.12.1-py3-none-win32.whl", hash = "sha256:d973fa626d4c8267848755bd0414211a456e99e125dcab147f24daa9e991a245", size = 10519495, upload-time = "2025-06-26T20:34:08.718Z" }, + { url = "https://files.pythonhosted.org/packages/cf/56/7158bd8d3cf16394928f47c637d39a7d532268cd45220bdb6cd622985760/ruff-0.12.1-py3-none-win_amd64.whl", hash = "sha256:9e1123b1c033f77bd2590e4c1fe7e8ea72ef990a85d2484351d408224d603013", size = 11547485, upload-time = "2025-06-26T20:34:11.008Z" }, + { url = "https://files.pythonhosted.org/packages/91/d0/6902c0d017259439d6fd2fd9393cea1cfe30169940118b007d5e0ea7e954/ruff-0.12.1-py3-none-win_arm64.whl", hash = "sha256:78ad09a022c64c13cc6077707f036bab0fac8cd7088772dcd1e5be21c5002efc", size = 10691209, upload-time = "2025-06-26T20:34:12.928Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/5d/9dcc100abc6711e8247af5aa561fc07c4a046f72f659c3adea9a449e191a/s3transfer-0.13.0.tar.gz", hash = "sha256:f5e6db74eb7776a37208001113ea7aa97695368242b364d73e91c981ac522177", size = 150232, upload-time = "2025-05-22T19:24:50.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/17/22bf8155aa0ea2305eefa3a6402e040df7ebe512d1310165eda1e233c3f8/s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:0148ef34d6dd964d0d8cf4311b2b21c474693e57c2e069ec708ce043d2b527be", size = 85152, upload-time = "2025-05-22T19:24:48.703Z" }, +] + +[[package]] +name = "scalene" +version = "1.5.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "jinja2" }, + { name = "numpy" }, + { name = "nvidia-ml-py", marker = "sys_platform != 'darwin'" }, + { name = "psutil" }, + { name = "pydantic" }, + { name = "rich" }, + { name = "wheel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/a4/e35a4e22a309ad6a886f0f3a66fd25ae1d0317d1aa81d21b79b3fe1b7cb9/scalene-1.5.51.tar.gz", hash = "sha256:ad33b6ce79239b5a6aff4ec78fa576fe2076b46f78c4c7e5fbc78a927b83374d", size = 9168270, upload-time = "2025-01-27T22:26:31.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/39/ace88bf09bca876ae9bd5009042dc95e4b9aae01c24dfc03cae33360d486/scalene-1.5.51-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:b9daaefdfc2c20d22aa1ad261cc5965fd73f2dd184fba126be4dc19aae44d141", size = 973804, upload-time = "2025-01-27T22:27:13.173Z" }, + { url = "https://files.pythonhosted.org/packages/db/f7/657b5fdbbf6eed0f1007f97f8992ef945dd3bc0dafb490cc5e660c40d374/scalene-1.5.51-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:4c12d714ce6664c2bd1adab72640bf65fbd9ca2c31164f26b99e898dfe53b754", size = 973209, upload-time = "2025-01-27T22:26:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/11/de/29a375bc1dc77033c5abbf7c465056557fb3f927eceb623c4b085f5dbf1d/scalene-1.5.51-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95fc48f2766f9e6c6b1d51942fe8949d422d0025c17471d80f4a67452508053a", size = 1247804, upload-time = "2025-01-27T22:22:38.842Z" }, + { url = "https://files.pythonhosted.org/packages/48/cb/df80c9f6b0b9b1748b9410e7a10a2f8f791d8588a1e0496bc7d0c6eecf71/scalene-1.5.51-cp312-cp312-win_amd64.whl", hash = "sha256:e53393ffe3fa67d1952b0b7eb8d7787b16f5569553433d6204416591c2e884a3", size = 862381, upload-time = "2025-01-27T22:22:47.273Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "shapely" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/8f/03929218f8d7003c3eafa5ffad1fb3f185459d336fa9cc31d3e67f442f97/shapely-2.0.3.tar.gz", hash = "sha256:4d65d0aa7910af71efa72fd6447e02a8e5dd44da81a983de9d736d6e6ccbe674", size = 280488, upload-time = "2024-02-16T14:22:38.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/2f/61a2e13dfdbba6a4610522d90e7eb55062c0f0a66dbd0cf24069d88ab67b/shapely-2.0.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e86e7cb8e331a4850e0c2a8b2d66dc08d7a7b301b8d1d34a13060e3a5b4b3b55", size = 2502679, upload-time = "2024-02-16T14:21:45.559Z" }, + { url = "https://files.pythonhosted.org/packages/c4/61/6a0a6170b73b49b66ad596d4745aba7b3b46f84ea6f42c72f0e304186ace/shapely-2.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c91981c99ade980fc49e41a544629751a0ccd769f39794ae913e53b07b2f78b9", size = 1433619, upload-time = "2024-02-16T14:21:47.634Z" }, + { url = "https://files.pythonhosted.org/packages/26/ad/81df31ec9e0065614482c54674b8c328627476aaeb61b081bfbd3ed1997f/shapely-2.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd45d456983dc60a42c4db437496d3f08a4201fbf662b69779f535eb969660af", size = 1274339, upload-time = "2024-02-16T14:21:49.889Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/992c8baa0636ca9b463be676f7f57dbc4dfc2806bd700fcabd23ad3429d3/shapely-2.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:882fb1ffc7577e88c1194f4f1757e277dc484ba096a3b94844319873d14b0f2d", size = 2443513, upload-time = "2024-02-16T14:32:35.348Z" }, + { url = "https://files.pythonhosted.org/packages/02/dc/f52a12fc1d20b5f871b58e8751ca1112cea72c487c4b055f6ff86beb5532/shapely-2.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9f2d93bff2ea52fa93245798cddb479766a18510ea9b93a4fb9755c79474889", size = 2529034, upload-time = "2024-02-16T14:21:52.125Z" }, + { url = "https://files.pythonhosted.org/packages/d8/53/1eb732b5c0eaf866878a4a7194e1d48bcf54b3ca1a0da14757e0a1a09101/shapely-2.0.3-cp312-cp312-win32.whl", hash = "sha256:99abad1fd1303b35d991703432c9481e3242b7b3a393c186cfb02373bf604004", size = 1293272, upload-time = "2024-02-16T14:21:54.194Z" }, + { url = "https://files.pythonhosted.org/packages/83/e6/15044a86a0d40ae57195ba2153d4078ca6855de3a93fd37542300f632c83/shapely-2.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:6f555fe3304a1f40398977789bc4fe3c28a11173196df9ece1e15c5bc75a48db", size = 1440001, upload-time = "2024-02-16T14:21:56.59Z" }, +] + +[[package]] +name = "simplejpeg" +version = "1.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/f0/c63f8be025a809ccb7383dff65f5b195ba14d5eb30a52cfaa3fd18f88536/simplejpeg-1.8.2.tar.gz", hash = "sha256:b06e253a896c7fc4f257e11baf96d783817cea41360d0962a70c2743ba57bc30", size = 5485180, upload-time = "2025-02-25T16:17:05.788Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/4c/3bd332dd3af05a18d48fe114e5c32876c85fadea8e521b06e5f93306ac8f/simplejpeg-1.8.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6185d3dbaac759f94663ad1b119f115e54fd472082d5ab0247292c06dfc4e8e0", size = 492706, upload-time = "2025-02-25T16:16:35.121Z" }, + { url = "https://files.pythonhosted.org/packages/18/46/d215d05d4302cde2f37c4d131a5cce96430d81543c024c0ce39e4b29df45/simplejpeg-1.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9feef167f7b6d77a1e00a3f08026876d9776c8b94b7f68b1ebe66c11270a486", size = 443258, upload-time = "2025-02-25T16:16:37.619Z" }, + { url = "https://files.pythonhosted.org/packages/8e/db/e00b23fd3db37d90f8c0ada5484e800d56d76123e647cdb365e75f4aa905/simplejpeg-1.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ea3ba9930ea991fa2a464efdcdd9c9db7e7f717949f4d291a87bdfbfc966a5", size = 445395, upload-time = "2025-02-25T16:16:41.42Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7f/f75153b2323d9bbb5eb4a4ff68f0c74bc5bfe5cf259d6b56a168847e24ce/simplejpeg-1.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e0217d767e0ecf7add67a41b9a368741dcc8e36ff89c1fc0a445cabc23f41b0", size = 425841, upload-time = "2025-02-25T16:16:43.057Z" }, + { url = "https://files.pythonhosted.org/packages/62/65/d78d840b9d9e923f85df27897308bd570ab51eae4221125bd0a337b3e73a/simplejpeg-1.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:098cf1eb0ab5c26c94793e190bb8abd4439536b271b865ad37f594b1c9bb8d8f", size = 306992, upload-time = "2025-02-25T16:16:44.579Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "1.3.24" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/ab/81bef2f960abf3cdaf32fbf1994f0c6f5e6a5f1667b5713ed6ebf162b6a2/SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519", size = 6353598, upload-time = "2021-03-30T23:04:30.273Z" } + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/89/c72771c81d25d53fe33e3dca61c233b665b2780f21820ba6fd2c6793c12b/tornado-6.5.1.tar.gz", hash = "sha256:84ceece391e8eb9b2b95578db65e920d2a61070260594819589609ba9bc6308c", size = 509934, upload-time = "2025-05-22T18:15:38.788Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/89/f4532dee6843c9e0ebc4e28d4be04c67f54f60813e4bf73d595fe7567452/tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7", size = 441948, upload-time = "2025-05-22T18:15:20.862Z" }, + { url = "https://files.pythonhosted.org/packages/15/9a/557406b62cffa395d18772e0cdcf03bed2fff03b374677348eef9f6a3792/tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6", size = 440112, upload-time = "2025-05-22T18:15:22.591Z" }, + { url = "https://files.pythonhosted.org/packages/55/82/7721b7319013a3cf881f4dffa4f60ceff07b31b394e459984e7a36dc99ec/tornado-6.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b77e9dfa7ed69754a54c89d82ef746398be82f749df69c4d3abe75c4d1ff4888", size = 443672, upload-time = "2025-05-22T18:15:24.027Z" }, + { url = "https://files.pythonhosted.org/packages/7d/42/d11c4376e7d101171b94e03cef0cbce43e823ed6567ceda571f54cf6e3ce/tornado-6.5.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253b76040ee3bab8bcf7ba9feb136436a3787208717a1fb9f2c16b744fba7331", size = 443019, upload-time = "2025-05-22T18:15:25.735Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f7/0c48ba992d875521ac761e6e04b0a1750f8150ae42ea26df1852d6a98942/tornado-6.5.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308473f4cc5a76227157cdf904de33ac268af770b2c5f05ca6c1161d82fdd95e", size = 443252, upload-time = "2025-05-22T18:15:27.499Z" }, + { url = "https://files.pythonhosted.org/packages/89/46/d8d7413d11987e316df4ad42e16023cd62666a3c0dfa1518ffa30b8df06c/tornado-6.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:caec6314ce8a81cf69bd89909f4b633b9f523834dc1a352021775d45e51d9401", size = 443930, upload-time = "2025-05-22T18:15:29.299Z" }, + { url = "https://files.pythonhosted.org/packages/78/b2/f8049221c96a06df89bed68260e8ca94beca5ea532ffc63b1175ad31f9cc/tornado-6.5.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:13ce6e3396c24e2808774741331638ee6c2f50b114b97a55c5b442df65fd9692", size = 443351, upload-time = "2025-05-22T18:15:31.038Z" }, + { url = "https://files.pythonhosted.org/packages/76/ff/6a0079e65b326cc222a54720a748e04a4db246870c4da54ece4577bfa702/tornado-6.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5cae6145f4cdf5ab24744526cc0f55a17d76f02c98f4cff9daa08ae9a217448a", size = 443328, upload-time = "2025-05-22T18:15:32.426Z" }, + { url = "https://files.pythonhosted.org/packages/49/18/e3f902a1d21f14035b5bc6246a8c0f51e0eef562ace3a2cea403c1fb7021/tornado-6.5.1-cp39-abi3-win32.whl", hash = "sha256:e0a36e1bc684dca10b1aa75a31df8bdfed656831489bc1e6a6ebed05dc1ec365", size = 444396, upload-time = "2025-05-22T18:15:34.205Z" }, + { url = "https://files.pythonhosted.org/packages/7b/09/6526e32bf1049ee7de3bebba81572673b19a2a8541f795d887e92af1a8bc/tornado-6.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:908e7d64567cecd4c2b458075589a775063453aeb1d2a1853eedb806922f568b", size = 444840, upload-time = "2025-05-22T18:15:36.1Z" }, + { url = "https://files.pythonhosted.org/packages/55/a7/535c44c7bea4578e48281d83c615219f3ab19e6abc67625ef637c73987be/tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7", size = 443596, upload-time = "2025-05-22T18:15:37.433Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[package.optional-dependencies] +brotli = [ + { name = "brotli", marker = "platform_python_implementation == 'CPython'" }, + { name = "brotlicffi", marker = "platform_python_implementation != 'CPython'" }, +] + +[[package]] +name = "uwsgi" +version = "2.0.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/f0/d794e9c7359f488b158e88c9e718c5600efdb74a0daf77331e5ffb6c87c4/uwsgi-2.0.30.tar.gz", hash = "sha256:c12aa652124f062ac216077da59f6d247bd7ef938234445881552e58afb1eb5f", size = 822560, upload-time = "2025-06-03T08:13:53.772Z" } + +[[package]] +name = "vine" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "wcmatch" +version = "10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bracex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[package]] +name = "webcolors" +version = "24.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064, upload-time = "2024-11-11T07:43:24.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934, upload-time = "2024-11-11T07:43:22.529Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, +] + +[[package]] +name = "wheel" +version = "0.45.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" }, +] + +[[package]] +name = "wrapt" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799, upload-time = "2025-01-14T10:33:57.4Z" }, + { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821, upload-time = "2025-01-14T10:33:59.334Z" }, + { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919, upload-time = "2025-01-14T10:34:04.093Z" }, + { url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721, upload-time = "2025-01-14T10:34:07.163Z" }, + { url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899, upload-time = "2025-01-14T10:34:09.82Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222, upload-time = "2025-01-14T10:34:11.258Z" }, + { url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707, upload-time = "2025-01-14T10:34:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685, upload-time = "2025-01-14T10:34:15.043Z" }, + { url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567, upload-time = "2025-01-14T10:34:16.563Z" }, + { url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672, upload-time = "2025-01-14T10:34:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865, upload-time = "2025-01-14T10:34:19.577Z" }, + { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" }, +] + +[[package]] +name = "wtforms" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/e4/633d080897e769ed5712dcfad626e55dbd6cf45db0ff4d9884315c6a82da/wtforms-3.2.1.tar.gz", hash = "sha256:df3e6b70f3192e92623128123ec8dca3067df9cfadd43d59681e210cfb8d4682", size = 137801, upload-time = "2024-10-21T11:34:00.108Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/c9/2088fb5645cd289c99ebe0d4cdcc723922a1d8e1beaefb0f6f76dff9b21c/wtforms-3.2.1-py3-none-any.whl", hash = "sha256:583bad77ba1dd7286463f21e11aa3043ca4869d03575921d1a1698d0715e0fd4", size = 152454, upload-time = "2024-10-21T11:33:58.44Z" }, +] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, + { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" }, + { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" }, + { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" }, + { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" }, + { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" }, + { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" }, + { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" }, + { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" }, + { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +] + +[[package]] +name = "zope-event" +version = "5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/c7/31e6f40282a2c548602c177826df281177caf79efaa101dd14314fb4ee73/zope_event-5.1.tar.gz", hash = "sha256:a153660e0c228124655748e990396b9d8295d6e4f546fa1b34f3319e1c666e7f", size = 18632, upload-time = "2025-06-26T07:14:22.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/ed/d8c3f56c1edb0ee9b51461dd08580382e9589850f769b69f0dedccff5215/zope_event-5.1-py3-none-any.whl", hash = "sha256:53de8f0e9f61dc0598141ac591f49b042b6d74784dab49971b9cc91d0f73a7df", size = 6905, upload-time = "2025-06-26T07:14:21.779Z" }, +] + +[[package]] +name = "zope-interface" +version = "7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/93/9210e7606be57a2dfc6277ac97dcc864fd8d39f142ca194fdc186d596fda/zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe", size = 252960, upload-time = "2024-11-28T08:45:39.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/0b/c7516bc3bad144c2496f355e35bd699443b82e9437aa02d9867653203b4a/zope.interface-7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:086ee2f51eaef1e4a52bd7d3111a0404081dadae87f84c0ad4ce2649d4f708b7", size = 208959, upload-time = "2024-11-28T08:47:47.788Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e9/1463036df1f78ff8c45a02642a7bf6931ae4a38a4acd6a8e07c128e387a7/zope.interface-7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21328fcc9d5b80768bf051faa35ab98fb979080c18e6f84ab3f27ce703bce465", size = 209357, upload-time = "2024-11-28T08:47:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/07/a8/106ca4c2add440728e382f1b16c7d886563602487bdd90004788d45eb310/zope.interface-7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6dd02ec01f4468da0f234da9d9c8545c5412fef80bc590cc51d8dd084138a89", size = 264235, upload-time = "2024-11-28T09:18:15.56Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ca/57286866285f4b8a4634c12ca1957c24bdac06eae28fd4a3a578e30cf906/zope.interface-7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e7da17f53e25d1a3bde5da4601e026adc9e8071f9f6f936d0fe3fe84ace6d54", size = 259253, upload-time = "2024-11-28T08:48:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/96/08/2103587ebc989b455cf05e858e7fbdfeedfc3373358320e9c513428290b1/zope.interface-7.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cab15ff4832580aa440dc9790b8a6128abd0b88b7ee4dd56abacbc52f212209d", size = 264702, upload-time = "2024-11-28T08:48:37.363Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c7/3c67562e03b3752ba4ab6b23355f15a58ac2d023a6ef763caaca430f91f2/zope.interface-7.2-cp312-cp312-win_amd64.whl", hash = "sha256:29caad142a2355ce7cfea48725aa8bcf0067e2b5cc63fcf5cd9f97ad12d6afb5", size = 212466, upload-time = "2024-11-28T08:49:14.397Z" }, +] + +[[package]] +name = "zstandard" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701, upload-time = "2024-07-15T00:18:06.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713, upload-time = "2024-07-15T00:15:35.815Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459, upload-time = "2024-07-15T00:15:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707, upload-time = "2024-07-15T00:15:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545, upload-time = "2024-07-15T00:15:41.75Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533, upload-time = "2024-07-15T00:15:44.114Z" }, + { url = "https://files.pythonhosted.org/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510, upload-time = "2024-07-15T00:15:46.509Z" }, + { url = "https://files.pythonhosted.org/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973, upload-time = "2024-07-15T00:15:49.939Z" }, + { url = "https://files.pythonhosted.org/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968, upload-time = "2024-07-15T00:15:52.025Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179, upload-time = "2024-07-15T00:15:54.971Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577, upload-time = "2024-07-15T00:15:57.634Z" }, + { url = "https://files.pythonhosted.org/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899, upload-time = "2024-07-15T00:16:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964, upload-time = "2024-07-15T00:16:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398, upload-time = "2024-07-15T00:16:06.694Z" }, + { url = "https://files.pythonhosted.org/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313, upload-time = "2024-07-15T00:16:09.758Z" }, + { url = "https://files.pythonhosted.org/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877, upload-time = "2024-07-15T00:16:11.758Z" }, + { url = "https://files.pythonhosted.org/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595, upload-time = "2024-07-15T00:16:13.731Z" }, +]